ЧАСТЬ B. Программирование в MS-DOS Глава 4. Структура прикладной программы Планирование прикладной программы в системе MS-DOS предполагает серьезный анализ размеров программы. Этот анализ позволит программисту определить, который из двух типов программ, поддерживаемых MS-DOS, бо- лее подходит для приложения. Структура .EXE-программы обеспечивает для больших программ преимуществами, вытекающими из дополнительных 512 байтов (или больше) заголовка, который обязательно присутствует в на- чале каждого EXE-файле. С другой стороны, за счет потери этих преиму- ществ структура .COM-программы не перегружает маленькую программу до- полнительными байтами заголовка. Поскольку .COM-программы начинают свое существование в форме .EXE-программ (прежде чем они будут отконвертированы с помощью EXE2BIN) и поскольку ряд аспектов прикладного программирования в среде MS-DOS не зввисит от типа используемых программ, серьезное понимание структуры .EXE-программы необходимо даже для тех программистов, кото- рые собираются писать только .COM-программы. Поэтому мы начнем наше изложение со структуры .EXE-программы, а затем остановимся на различи- ях между .COM-программами и .EXE-программами, включая ограничения на структуру и содержание .COM-программ. 1. .EXE-программа .EXE-программы обладают рядом преимуществ по сравнению с .COM-программами. Предпочтение .EXE-формату целесообразно отдать в следующих случаях: - очень большие программы; - большое число сегментов; - оверлейная организация программы; - константы сегментов и "удаленные" адресные константы; - сложные вызовы программ; - возможность перевода программ в защищенный режим (protected mode) MS OS/2. Основные преимущества .EXE-формата обеспечиваются с помощью заго- ловка файла. Самым важным является то, что заголовок соержит информа- цию, позволяющую программе выполнять прямую адресацию сегментов - очень важное требование в случаях, когда размер программы превышает 64 Кбайт. В заголовке файла имеется также информация для MS-DOS о том, сколько памяти требуется для размещения программы. Эта информация поз- воляет не выделять лишнюю память программе, что очень важно, если в будущем планируется эффективно использовать программу в режиме MS OS/2. Прежде чем подробно рассмотреть структуру .EXE-программы, посмот- рим, как функционируют такого рода программы. 1.1. Передача управления .EXE-программе На рис.4-1 показано, как .EXE-программа может появиться в па- мяти, когда MS-DOS впервые передает ей управление. На диаграмме пред- ставлено предпочтительное, с точки зрения фирмы Microsoft, размещение сегментов. Прежде чем передать управление .EXE-программе, MS-DOS инициирует различные области памяти и некоторые регистры микропроцессора. Ниже объясняется, какие действия можно ожидать от MS-DOS прежде, чем управ- ление будет передано .EXE-программе. 1.1.1. Префикс сегмента программы Префикс сегмента программы (PSP) не является частью программного кода. Эта специальная страница памяти длиной 256 байт (16 парагнрафов) строится системой MS-DOS перед каждой .EXE и .COM-программой в процес- се загрузки её в память. Хотя PSP содержит в себе некоторые используе- мые в более новых программах поля, фактически он является элементом системы CP/M, - фирма Microsoft сохранила PSP для того, чтобы облег- чить перенос огромного числа программ, работающих под управлением CP/M, в среду MS-DOS. На рисунке 4-2 приведен состав полей PSP. PSP:0000H (Вектор завершения [старый перезапуск из памяти - "теп- лый старт"]). PSP начинается командой INT 20H семейства 8086, которую программа может использовать для передачи управления обратно MS-DOS. ----------------T--------------------------¬ў SP ¦ /¦ ¦ ¦ ¦ ¦ Любые сегменты класса ¦ ¦ ¦ ¦ STACK ¦ ¦ ¦ +--------------------------+ў SS ¦ Все ¦ ¦ ¦ ¦ ¦ ¦ Любые сегменты класса ¦ ¦ сегменты ¦ ¦ BSS ¦ ¦ ¦ +--------------------------+ ¦ объявлены { ¦ ¦ ¦ ¦ ¦ Любые сегменты DGROUP, не¦ ¦ как часть ¦ ¦ представленные в других ¦ ¦ ¦ ¦ местах ¦ ¦ группы ¦ +--------------------------+ ¦ ¦ ¦ ¦ ¦ DGROUP ¦ ¦ Любые сегменты класса ¦ ¦ \¦ BEGDATA ¦ +---------------+--------------------------+ў IP Начало сег- ¦ Любые сегменты с именами классов, ¦ мента прог- ¦ заканчивающимися на CODE ¦ раммы Ў+------------------------------------------¦ў CS (загрузоч- | Префикс сегмента программы (PSP) | ный модуль) L - - - - - - - - - - - - - - - - - - - - --ў DS.ES Рис. 4-1. EXE-программа: диаграмма представления памяти с указателями регистров PSP включает эту команду со смещением 00H, потому что это был адрес WBOOT (Перезапуск их памяти/Завершение) вектора в системе CP/M, и программы CP/M обычно завершилась переходом на этот вектор. Такой ме- тод завершения не должен использоваться в новых программах. Смотри ни- же раздел "Завершение .EXE-программы". PSP:0002H (Адрес последнего выделенного программе сегмента). MS-DOS вводит слово со смещением 02H в PSP. Оно содержит адрес сегмен- та для параграфа, следующего за блоком памяти, выделенным программе. Этот адрес должен использоваться только для определения размера или конца блока памяти, выделенного программе; его нельзя рассматривать как свободную память, которую может использовать программа. Чаще всего этот адрес НЕ указывает на свободную память, потому что любая свобод- ная память уже была бы распределена программе, если только программа не была присоединена переключателем /CPARMAXALLOC. Даже если использу- ется /CPARMAXALLOC, MS-DOS постарается поместить программу только в такой блок памяти, который ей требуется. Правильные программы должны получать дополнительную память только с помощью функций MS-DOS, специ- ально предназначенных для этой цели. PSP:0005H (Вектор обращения к функциям MS-DOS [старый BDOS]). Смещение 05Н также пришло из системы CP/M. По этому адресу находится (межсегментная) команда вызова обработчика обращений к MS-DOS для се- мейства процессрорв 8086. (В системе CP/M по этому адресу находился вектор Базовой дисковой операционной системы (BDOS), который выполнял аналогичную функцию.) Этот вектор не должен использоваться для вызова MS-DOS в новых программах. В разделе "Системные вызовы" данного руко- водства объясняется правильный способ обращения к MS-DOS. MS-DOS стро- ит этот вектор только для поддержки программ, написанных для CP/M, и поэтому реализует с его помощью только соответствующие функции CP/M (00-24H). PSP:000AH (Область сохранения вектора прерывания программы, ис- пользующей 22Н, 23Н и 24Н ). MS-DOS использует области со смещениями от 0AH до 15Н для сохранения трех специфических программных векторов прерывания. MS-DOS должна сохранить эти вектора, потому что это обес- печивает любой программе возможность выполнять любую другую программу (называемую подчиненным процессом), используя функцию вызова MS-DOS, которая возвратит управление исходной программе, как только завершится вызываемая программа. Так как исходная программа продолжит работу пос- ле завершения вызванной программы, MS-DOS должна восстановить эти три вектора прерывания для исходной программы, если вызванная программа изменила их. Указанные три вектора включают вектор обработчика прог- раммных прерываний (Прерывание 22Н), вектор обработчика прерываний Control_C/Control_Break (Прерывание 23Н) и вектор обработчика крити- ческих ситуаций (Прерывание 24Н).Система MS-DOS сохраняет исходное значение этих векторов в PSP подчиненной программы в двойном слове со смещениями 0AH - для вектора обработчика программных прерываний, 0ЕН - для вектора обработчика прерываний Control_C/Control_Break и 12Н - для вектора обработчика критических ситуаций. PSP:002CH (Адрес сегмента описания среды операционной системы). В версии системы MS-DOS 2.0 и более поздних слово со смещением 2СН со- держит очень полезную информацию, которую программа может отыскать в PSP - адрес сегмента первого параграфа описания среды операционной системы MS-DOS. Этот указатель позволяет программе отыскать описание конфигурации операционной системы или строки описания путей доступа, занесенные туда пользователями с помощью команды SET. PSP:0050H (Новый вектор вызова MS-DOS). Многие программисты игно- рируют содержимое по адресу со смещением 50Н. Здесь размещается коман- да INT 21H, за которой следует RETF, .EXE-программы могут использовать этот адрес для удаленного вызова обработчика команд MS-DOS. Конечно, программа может просто выдать непосредственно команду INT 21H, что го- раздо короче и быстрее, чем вызов 50Н. В отличие от вызова со смещени- ем 05Н, вызов со смещением 50Н обеспечивает доступ ко всему многообра- зию функций MS-DOS. PSP:005CH (Блок 1 управления файлом по умолчанию) и PSP:006CY (Блок 2 управления файлом по умолчанию). Система MS-DOS анализирует первые два параметра, которые пользователь вводит в командной строке вслед за именем программы. Если первый параметр представляет собой полное (сокращенное) имя файла в MS-DOS (перед именем может указывать- ся специальный символ, но не каталог), система анализирует смещения 5СН и 6BH, по которому размещаются первые 16 байтов блока управления файлом (FCB) для указанного неоткрытого файла. Если второй параметр также является именем файла в MS-DOS, система размещает первые 16 бай- тов FCB для второго неоткрытого файла по адресам со смещениями от 6СН до 7ВН. Если в качестве части любого из имен файлов пользователь ука- зывает каталог, MS-DOS инициирует только код устройства в соответству- ющем FCB. Многие программисты уже не пользуются этим средством, потому что обработка файла с помощью FCB не позволяет учитывать путь доступа к каталогу и другие более новые средства системы MS-DOS. Так как для открытого файла длина FCB равна 37 байтам, открытие первого FCB по адресу со смещением 5СН вызывает его увеличение с 16 до 37 байтов и перекрытие второго FCB. Аналогично, открытие второго FCB по адресу со смещением GCH вызывает его расширение и перекрытие первой части остатка команды ипринимаемой по умолчанию дисковой области пере- дачи данных (DTA). (Конец команды и область DTA подробно описываются ниже.) Для того, чтобы использовать значение полей обоих построенных по умолчанию FCB, программа должна скопировать их в пару полей разме- ром по 37 байтов, размещенных в области данных самой программы. Прог- рамма может использовать первое FCB без перезаписи только после пере- мещения второго FCB (если это необходимо) и только для выполнения пос- ледовательного чтения и записи. Для выполнения произвольных команд чтения и записи с первым FCB программист должен либо переписать это FCB, либо изменить заданный по умолчанию адрес DTA. В противном случае поле записи первого FCB перекроет начало DTA. Смотри руководство ПРОГ- РАММИРОВАНИЕ В ОПЕРАЦИОННОЙ СРЕДЕ MS-DOS: Программирование в MS-DOS: Управление файлами и записями. PSP:0080H (Остаток команды и принимаемая по умолчанию Область пе- редачи данных). Область передачи данных занимает всю вторую половину (128 байтов) PSP. Система MS-DOS использует эту область памяти в ка- честве буфера передачи данных, если программа использует функции обра- ботки файла с помощью FCB. Опять-таки, MS-DOS унаследовала это поле от системы CP/M. (MS-DOS предлагает функцию, с помощью которой программа может изменить текущий адрес DTA. Смотри руководство СИСТЕМНЫЕ ФУНКЦИИ : Прерывание 21Н : Функция 1АН.) Поскольку область DTA не используется до тех пор, пока программа не затребует какой-либо обработки файла, MS-DOS помещает в эту область остаток команды, чтобы программа могла его проанализировать. Остаток команды представляет собой любой текст, который пользователь может ввести за именем программы в ходе выполне- ния программы. Как правило, первым символом остатка команды является пробел в коде ASCII (20H), но им может быть и любой другой символ, ко- торый трактуется MS-DOS как разделитель. MS-DOS помещает текст остатка команды, начиная со смещения 81Н и всегда в конце текста проставляет символ "возврат каретки" кода ASCII (0DH). Кроме того, система помеща- ет длину остатка команды в поле со смещением 80Н. Эта длина включает все символы, кроме завершающего 0DH. Например, командная строка C>DOIT WITH CLASS вызовет выполнение программы DOIT, а поле PSP:0080H будет иметь следу- ющее содержимое 0В 20 50 49 54 48 20 43 4С 41 53 53 0D len Sp W I T H Sp C L A S S Cr x0H xIH x2H x3H x4H x5H x6H x7H x8H x9H xAH xBH xCH xDH xEH xFH ---------T-------T----T-------------------T---------------T------- ¦ ¦ Адрес,¦Заре¦ ¦ ¦ ¦INT 20H ¦следую-¦зер-¦ Удаленный вызов ¦ Адрес предыду-¦ Адрес ¦ ¦щий за ¦виро¦ обработчика функ- ¦щего завершения¦предыду- 0xH¦ ¦концом ¦вано¦ ций MS-DOS ¦ ¦ щего ¦ ¦програм¦ ¦ ¦ ¦прерыва- ¦ ¦ мы ¦ ¦ ¦ ¦ ¦0CDH¦20H¦seg¦seg¦ ¦9AH¦ofs¦ofs¦seg¦seg¦ofs¦ofs¦seg¦seg¦ofs¦ofs¦ ¦ ¦ ¦lo ¦hi ¦ ¦ ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦ L----+---+---+---+----+---+---+---+---+---+---+---+---+---+---+---- ¦Адрес предыдущей¦ ...ния ¦критической ошиб¦ Зарезервировано ... 1xH Ctrl-C¦ ки ¦ ¦seg ¦seg¦ofs¦ofs¦seg ¦seg¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L----+---+---+---+----+---+---+---+---+---+---+---+---+---+---+---- ¦Сегмент¦Зарезер ...Зарезервировано ¦ описа-¦ виро- ¦ния сре¦ вано... 2xH ¦ ды(*) ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦seg¦seg¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦lo ¦hi ¦ ¦ ¦ L---+-----+---+---+---+---+---+---+---+---+---+---+---+---+---+---- ...Зарезервировано... 3xH ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L---+-----+---+---+---+---+---+---+---+---+---+---+---+---+---+---¦ ...Зарезервировано ¦ 4xH ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ +---+----+----+---+---+---+---+---+---+---+---+---+---+---+---+---- ¦ INT 21H и ¦ Зарезервировано ¦ Первый FCB... 5xH¦ RETF ¦ ¦ ¦0CDN¦21H¦CNBN¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ d ¦ F ¦ i ¦ l ¦ L----+---+----+---+---+---+---+---+---+---+---+---+---+---+---+---- ...Первый блок управления файлами (FCB) ¦ Второй FCB... ¦ 6xH¦ e ¦ n ¦ a ¦m ¦ e ¦ E ¦ x ¦ t ¦00H¦00H¦00H¦00H¦ d ¦ F ¦ i ¦ l ¦ L----+---+----+---+---+---+---+---+---+---+---+---+---+---+---+---+ ...Второй блок управления файлами (FCB) ¦Зарезервировано¦ ¦ ¦ 7xH¦ e ¦n ¦ a ¦ m ¦ e ¦ E ¦ x ¦ t ¦00H¦00H¦00H¦00H¦ ¦ ¦ ¦ ¦ +----+---+----+---+---+---+---+---+---+---+---+---+---+---+---+---- ¦ Остаток команды и область передачи данных (DTA) 8xH¦ (продолжается до 0FFH)... ¦Len ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ L----+---+----+---+---+---+---+---+---+---+---+---+---+---+---+---- Рис. 4-2. Префикс сегмента программы (PSP) (*) П_р_и_м_е_ч_а_н_и_е. Сегмент описания среды присутствует только для версии MS-DOS 2.0 и более поздних. 1.1.2. Стек Так как программы .EXE-типа не создавались в среде CP/M, в систе- ме MS-DOS, естественно, предполагается, что они будут работать вполне определенным образом. Например, в среде MS-DOS .EXE-программа строит свой собственный стек. (На рис. 4-1 стек программы изображен в виде самого верхнего квадрата диаграммы.) Компиляторы фирмы Microsoft для языков высокого уровня сами стро- ят стек программы, однако если программист работает с языком Ассембле- ра, он должен сам объявить один или несколько объединительных (combine) сегментов типа STACK. В случае, когда программист объявляет несколько сегментов типа стек, возможно в различных исходных модулях, редактор объединит их в один большой сегмент. Смотри ниже раздел "Уп- равление структурой .EXE-программ" . Многие программисты объявляют свои сегменты типа стек как предва- рительно инициируемые с некоторой отличительной повторяющейся строкой, например, #STACK. В таком случае можно найти стек программы в памяти (с помощью системы отладки типа DEBUG) и определить, сколько реального пространства стека использует программа. С другой стороны, если стек непроинициализирован и присоединен в конец .EXE-программы, ему не пот- ребуется место в .EXE-файле. (Будет понятно, что это имеет определен- ный смысл, когда мы рассмотрим более подробно структуру .EXE-файла.) З_а_м_е_ч_а_н_и_е. Если несколько сегментов типа стек объявлены в различных .ASM-файлах, Редактор объектных модулей (LINK) фирмы Microsoft размещает в виде непрервыного участка памяти все стековое пространство, объявленное во всех исходных модулях, однако инициализа- ция указанными в модулях данными выполняется с перекрытием данных по- модульно в самом конце этого комбинированного сегмента. Существенное различие между .COM- и .EXE-программами состоит в том, что система MS-DOS прежде, чем передать управление программе, предварительно инициирует стек .COM-программы адресом завершения. Для .EXE-программ MS-DOS не делает этого, поэтому .EXE-программа не может просто выполнить команду RET семейства процессоров 8086 для завершения своей работы. З_а_м_е_ч_а_н_и_е. В ассемблерных файлах, порожденных компилято- ром для С-программ и программ на других языках высокого уровня, появ- ление команды RET в конце главной функции/подпрограммы/процедуры может показаться странным. Кроме того, MS-DOS не заносит никакого адреса возврата в стек. Компилятор помещает команду RET в конце г_л_а_в_н_о_й программы, потому что она не получает управление непосредственно от MS-DOS. От MS-DOS получает управление программа инициализации библио- тек, а она уже потом вызывает г_л_а_в_н_у_ю программу. Когда г_л_а_в_н_а_я программа выполняет команду RET, управление передается программе, обслуживающей библиотеки, а она уже в свою очередь возвра- щает его MS-DOS стандартным образом. 1.1.3. Предварительно распределенная память В процессе загрузки .EXE-программы система MS-DOS выполняет неко- торую последовательность действий для определения количества памяти, которое необходимо предварительно выделить для программы. Во-первых, MS-DOS прочитывает два числа, которые редактор поместил рядом в нача- лом заголовка .EXE-программы: первое число - MINALLOC - указывает ми- нимальное количество дополнительной памяти, которое требуется програм- ме, чтобы начать работу; второе число - MAXALLOC - указывает макси- мальное количество дополнительной памяти, которое программа хотела бы получить перед началом своей работы. Затем MS-DOS отыскивает наиболь- ший свободный блок имеющейся памяти. Если размер образа программы в .EXE-файле плюс значение MINALLOC превышает размер найденного блока памяти, MS-DOS возвращает код ошибки процессу, пытающемуся загрузить программу. Если это процесс COMMAND.COM, он выводит на экран сообщение об ошибке вида "Программа очень велика. Разместиться в памяти не мо- жет" (Program too long to fit in memory) и завершает обработку запроса пользователя. Если размер блока превышает размер программы с учетом MINALLOC, тогда MS-DOS сравнивает размер блока памяти с длиной образа программы плюс значение MAXALLOC. Если размер свободного блока превы- шает максимальное требование программы, MS-DOS выделяет память, равную максимальному запросу; в противном случае система распределяет весь блок. Затем система строит PSP в начале этого блока и загружает образ программы из .EXE-файла в оперативную память вслед за PSP. Такая процедура гарантирует, что дополнительная память, распреде- ленная программе, будет расположена непосредственно за образом прог- раммы. Однако этого нельзя сказать о любой другой памяти, которую MS-DOS выделяет программа в ответ на обращения к функциям MS-DOS в процессе ее выполнения. Только обращения к функциям MS-DOS об увеличе- нии первоначального распределения могут гарантировать, что дополни- тельная память будет получена непрерывным куском. (Естественно, что результат выполнение такого обращения зависит от наличия свободной па- мяти, расположенной следом за первичным распределением.) Программисты, работающие с .EXE-программами, часто удивляются, что отсутствуют ключевые слова или переключатели компилятора/ассембле- ра, связанные с полем MINALLOC (и даже MAXALLIC). Дело в том, что программист никогда явно не задает значение поля MINALLOC, потому что редактор связей LINK устанавливает MINALLOC равным общей длине всех инициализированных данных и/или стековых сегментов, присоединенных в самом конце программы. Поле MINALLOC дает возможность компилятору оп- ределить размер инициализированных полей данных в загрузочном модуле, не включая в него сами поля, что уменьшает размер файла .EXE-програм- мы. Компиляторы фирмы Microsoft с языков высокого уровня делают это автоматически; программисты, работающие с языком ассемблера, должны дать некоторые указания редактору. З_а_м_е_ч_а_н_и_е. Начинающие и даже опытные программисты на язы- ке ассемблера могут легко ошибиться при адресации полей, пытаясь раз- местить данные после текста программы в исходном файле. Чтобы этого не произошло программистам следует использовать директивы ассемблера SEGMENT и GROUP. Смотри ниже раздел "Управление структурой .EXE-прог- раммы". Редактор не знает хорошего способа определения точного значения величины MAXALLOC для программы. Поэтому редактор использует величину FFFFH, и, тем самым, заставляет MS-DOS выделить самый большой блок свободной памяти для прогаммы - как правило, это - вся имеющаяся в на- личии свободная память. Если программа не освобождает память, которую она не использует, она тем самым не дает возможности разместиться су- первизорным программам, выполняющимся в мультизадачном режиме, таким как IBM TopView. Поэтому правило формулируется следующим образом: хо- рошо написанная программа должна освобождать ненужную ей память в про- цессе инициализации. К сожалению, такой подход к резервированию памяти является несостоятельным, если многозадачный супервизор позволяет заг- ружать в память несколько программ, не выполняя их. Поэтому хорошо на- писанными считаются программы, имеющие правильное значение MAXALLOC. И наконец, последние версии редактора LINK фирмы Microsoft имеют переключатель /CPARMAXALLOC, позволяющий задавать максимальное коли- чество памяти, необходимое программе. С помощью переключателя /CPARMAXALLOC можно присвоить MAXALLOC значение меньшее, чем MINALLOC. Например, если задать MAXALLOC равным 1 (/CP:1), MS-DOS выделит про- рамме только дополнительные параграфы, указанные в MINALLOC. Кроме то- го, почти для всех своих компиляторов языков фирма Microsoft поставля- ет программу EXEMOD. Эта программа позволяет модифицировать поля MAXALLOC в заголовках уже готовых .EXE-программ. Смотри ниже раздел "Модификация заголовка .EXE-файла". 1.1.4. Регистры Рисунок 4-1 дает представление о том, как система MS-DOS устанав- ливает регистры в машинах семейства 8086, прежде чем передать управле- ние .EXE-программе. MS-DOS определяет большинство исходных значений регистров на основе информации, помещаемой редактором в заголовок .EXE-файла, находящийся в его начале. Система MS-DOS заносит в регистр SS адрес сегмента (параграфа) - начала любого из сегментов, для которых объявлен комбинированный тип STAСK, а в регистр SP - смещение от значения SS к байту, следующему непосредственно за всеми сегментами объединительного типа STAСK. (Если не объявлено ни одного сегмента типа стек , MS-DOS устанавливает SS:SP в CS:0000.) Так как в архитектуре машин семейства 8086 стек занимает память, начиная с больших значений адресов к меньшим, регистры SS:SP задают начальный адрес стека. Следовательно, если программист объявля- ет стек при подготовке программы на языке Ассемблера, программа не должна инициализировать регистры SS и SP. Компиляторы фирмы Microsoft с языков высокого уровня выполняют функцию создания стековых сегментов автоматически. В обоих случаях редактор задает начальное значение ре- гистров SS и SP и помещает их в заголовок, находящийся в начале файла .EXE-программы. В отличие от регистров SS и SP , система MS-DOS не устанавливает регистры DS и ES на какие-либо области данных .EXE программы. Вместо этого она устанавливает их на начало PSP. Делает она это по двум при- чинам: во-первых, через регистры DS и ES система MS-DOS сообщает прог- рамме адрес PSP; во-вторых, многие програмы начинают свою работу с анализа остатка команды запуска, помещенного в PSP. Так как в начале работы программы регистр DS не указывает на сегменты данных, программа должна инициализировать DS (и возможно ES) адресом сегментов данных, прежде чем обратиться к любому полю в этих сегментах. В отличие от .COM-программ, .EXE-программы легко справляются с такой задачей, так как могут напрямую адресоваться к сегментам, например, следующим обра- зом: MOV AX,SEG ИМЯ СЕГМЕНТА ИЛИ ГРУППЫ ДАННЫХ MOV DS,AX MOV ES,AX Программы, написанные на языке высокого уровня, не должны инициа- лизировать и заботиться о содержимом регистров DS и ES; этим занимают- ся компилятор и программы обслуживания библиотек. Кроме установки регистров DS и ES на начало PSP, система MS-DOS задает значения регистров AH и AL, которые определяют наличие иденти- фикаторов устройств, находящихся в двух FCB, расположенных в PSP. MS-DOS присваивает регистру AL значение 0FFH, если в первом FCB по ад- ресу PSP:005CH находится идентификатор несуществующего устройства, в противном случае AL равен 0. Аналогичным образом содержимое регистра AH характеризует идентификатор устройства, находящийся во втором FCB по адресу PSP:006CH. Когда система MS-DOS анализирует первые два параметра командной строки, следующие за именем программы, с целью построения двух FCB, она трактует любой символ, за которым идет двоеточие, как префикс уст- ройства. Если префикс устройства содержит строчную букву (в коде ASCII от а до z), MS-DOS сначала переводит ее в прописную (в коде ASCII от A до Z).Затем от полученного значения отнимается 40Н. В результате бук- венные префиксы устройств от А до Z переводятся в коды этих устройств от 01Н до 1АН, что и требуется для FCB. И, наконец, MS-DOS помещает полученный код в соответствующее место FCB. Такая процедура не исключает помещения ошибочных имен устройств в FCB. Например, MS-DOS может воспринять префикс устройства !: и помес- тить соответствующий код 0Е1Н в FCB (!=21Н; 21Н-40Н=0Е1Н). Однако поз- же MS-DOS проверит, действительно ли указанный код соответствует ре- альному устройству, подключенному к компьютеру, и передаст программе значение 0FFH в известном регистре (AL или АН), если это не так. Побочный эффект такой процедуры состоит в том, что MS-DOS воспри- нимает как правильный идентификатор @:, потому что вычитание величины 40Н преобразует значение символа @ (40Н) в 00Н. MS-DOS воспринимает 00Н как правильное значение, потому что код устройства 00Н соответст- вует текущему устройству, заданному по умолчанию. MS-DOS сохранит код устройства 00Н в FCB, а не будет преобразовывать его в код заданного по умолчанию устройства, потому что функции MS-DOS, работающие с FCB воспринимают код 00Н. И наконец, система MS-DOS инициализирует регистры CS и IP, пере- давая управление во входную точку программы. Программы, обработанные компиляторами с языков высокого уровня, обычно получают управление от программы инициализации библиотек. Программист, желающий написать программу на языке ассемблера с использованием макроассемблера фирмы Microsoft (MASM), может любую метку внутри программы объявить входной точкой, если поместить ее в качестве операнда оператора END, завершаю- щего программу: END ENTRY_POINT_LABEL (имя_точки_входа) В файле, содержащем несколько исходных модулей, только один может иметь метку в качестве операнда оператора END. Если несколько исходных файлов имеют такую метку, редактор рассматривает в качестве точки вхо- да первую встретившуюся ему метку. Остальные регистры (BX, CX, DX, BP, SI и DI) могут иметь произ- вольные значения в момент, когда программа получает управление от MS-DOS. Опять же, программисты, работающие с языками высокого уровня, могут и не знать этого, - компилятор и программы поддержки библиотек сами контролируют ситуацию. Однако программисты, работающие с языком ассемблера, должны знать этот факт. Это может пригодиться когда-нибудь в будущем, если написанная программа в одних случаях будет работать, а в других - нет. Очень часто такие отладочные системы, как DEBUG и SYMDEB, инициа- лизируют неинициализируемые регистры некоторыми недокументированными значениями. Например, некоторые отладчики устанавливают регистр BP в ноль, прежде чем начать выполнение программы. Однако,программа не дол- жна рассчитывать на такого рода действия, поскольку MS-DOS ничего по- добного не гарантирует. В такого рода ситуациях программа может отлич- но работать в системе отладки и совсем не работать под управлением MS-DOS. 1.2. Завершение .EXE-программы После того как .EXE-программа получила управление от MS-DOS, вы- полнила все необходимые действия, она должна вернуть управление систе- ме MS-DOS. В результате развития в операционной системе MS-DOS появи- лось пять способов завершения программы, не считая тех способов, когда MS-DOS разрешает программе завершиться, но остаться размещенной в па- мяти. Прежде чем использовать один из методов завершения, поддерживае- мых MS-DOS, программа всегда должна закрыть все открытые файлы, и прежде всего те, в которые выполнялась запись данных или размеры кото- рых изменились. В версиях MS-DOS 2.0 и более поздних система сама зак- рывает все файлы, открытые с помощью драйверов. Однако считается хоро- шим тоном в программировании, если програма не надеется на операцион- ную систему и сама закрывает все свои открытые файлы. Кроме того, программы, использующие разделяемые файлы в версиях MS-DOS 3.0 и выше должны отменить все захваты файлов, прежде чем закрыть их и завершить- ся. 1.2.1. Процесс завершения с помощью функции, воз- вращающей код завершения Из пяти способов завершения для программ, работающих под управле- нием версий MS-DOS 2.0 и более поздних рекомендуется только Процесс завершения с прерыванием 21Н и функцией, возвращающей код завершения (4СН). Указанный способ является самым простым и может использоваться для завершения любой программы, независимо от ее структуры и содержи- мого регистра сегмента. Процесс завершения может быть просто запрог- раммирован следующим образом: MOV AH,4CH ;поместить в AH код функции завершения MOV AL,RETURN_CODE ;поместить в AL код завершения INT 21H ;обратиться к MS-DOS для завершения программы В примере в регистр АН загружается адрес процесса завершения с помощью функции, возвращающей код завершения. В регистр AL загружается код завершения. Как правило, код завершения содержит причину, по кото- рой завершилась программа или результат выполненной программой опера- ции. Программа, вызвавшая другую программу, получив снова управление, может проанализировать код завершения подчиненной программы, если пос- ледняя завершилась описанным выше способом. В свою очередь подчиненная программа может анализировать RETURN_CODE своего подчиненного процес- са. Если какая-либо программа завершается указанным способом и управ- ление возвращается MS-DOS, можно использовать командный (.BAT) файл, чтобы тестировать код завершения программы с помощью оператора IF ERRORLEVEL. Приняты всего лишь два общих соглашения, касающихся значений RETURN_CODE: во-первых, значение кода завершения, равное 00Н, указыва- ет на нормальное, безошибочное завершение программы; во-вторых, увели- чение значений кода завершения свидетельствует об увеличении серьез- ности условий, которые вызвали завершение программы. Например, компи- лятор присваивает величине RETURN_CODE значение 00Н, если в исходном файле не найдено ошибок; значение 01Н, если будут выданы предостерега- ющие предупреждения, значение 02Н, если обнаружены серьёзные ошибки. Если программа не возвращает никаких специальных значений кода завершения, следующих команд будет достаточно, чтобы завершить прог- рамму со значением RETURN_CODE, равным 00Н: MOV AX,4C00H INT 21H Кроме того, что процесс завершения с помощью функции, возвращаю- щей код завершения, является общепризнанным способом завершения прог- рамм, он ещё и очень простой по сравнению с другими методами, потому что все они трубуют, чтобы регистр CS указывал на начало PSP, когда завершается .EXE-программа. Такое ограничение осложняет работу .EXE-программ, потому что необходимо использовать адреса сегментов, отличные от адреса PSP. Единственное осложнение, связанное с процессом завершения с по- мощью функции, возвращающей код завершения, состоит в том, что он не доступен в более ранних версиях системы MS-DOS , чем 2.0; следователь- но его нельзя использовать, если программа должна быть совместима с ранними версиями MS-DOS. Однако на рис.4-3 показано, как программа мо- жет использовать указанный метод завершения, оставаясь при этом сов- местимой с ранними версиями MS-DOS. Смотри ниже раздел "Перезапуск из памяти/Вектор завершения". _____________________________________________________________________ TEXT SEGMENT PARA PUBLIC 'CODE' ASSUME CS:TEXT,DS:NOTHING,ES:NOTHING,SS:NOTHING TERM_VECTOR DD ? ENTRY_PROC PROC FAR ;сохранить указатель на вектор завершения в PSP MOV WORD PTR CS:TERM_VECTOR+0,0000H ;сохранить смещение к векто- ;ру перезапуска из памяти MOV WORD PTR CS:TERM_VECTOR+2,DS ;сохранить адрес сегмента PSP ;* * * * * Здесь расположите основную программу * * * * * ;определить версию MS-DOS, обойти завершение для версии, более ранней, ;чем 2.0 MOV AH,30h ;загрузить код функции определения версии MS-DOS INT 21h ;обратиться к MS-DOS для получения номера версии OR AL,AL ;версия более ранняя, чем 2.0 ? JNZ TERM_0200 ;нет ;завершение для версий MS-DOS, более ранних, чем 2.0 JMP CS:TERM_VECTOR ;перейти к вектору перезапуска из памяти ;завершение для версий MS-DOS 2.0 и более поздних TERM_0200: MOV AX,4C00h ;загрузить код функции завершения и код завершения INT 21h ;обратиться к MS-DOS для завершения программы ENTRY_PROC ENDP TEXT ENDS END ENTRY_PROC ;определить точку входа Рис. 4-3. Правильное завершение в любой версии MS-DOS _____________________________________________________________________ 1.2.2. Прерывание завершения программы До версии MS-DOS 2.0 завершение программы означало выполнение ко- манды INT 20H - прерывание по завершению программы. В новом способе завершения программы команда INT 20H была устранена по двум причинам: во-первых, она не обеспечивала возможности возврата кода завершения; во-вторых, требовалось, чтобы регистр CP содержал адрес PSP, прежде чем будет выполнена команда INT 20H. Ограничение, связанное со значением регистра CS, не составляло особых сложностей для .COM-программ, потому что они работают, когда регистр CS указывает на начало PSP. В процессе работы .EXE-программы CS указывает на различные сегменты программного кода и значение его не может быть произвольно изменено, когда программа готова к завершению. Именно поэтому лишь небольшое число .EXE-программ выполняет прерывание по завершению программы прямо из своего программного кода. Вместо это- го они, как правило, используют метод завершения, описанный ниже. 1.2.3. Перезапуск из памяти/Вектор завершения Предыдущее описание структуры PSP кратко касалось одного более старого метода завершения .EXE-программ: по смещению 00Н в PSP нахо- дится команда INT 20H, на которую программа может перейти, если она хочет завершиться. В системе MS-DOS такая технология принята , чтобы обеспечить поддержку большого числа CP/M-программ, перенесенных в MS-DOS. В системе CP/M по этому адресу PSP размещается вектор переза- пуска из памяти, потому что операционная система CP/M всегда перезаг- ружалась с диска всякий раз, когда программа завершалась. Так как по смещению 00Н в PSP находится команда INT 20H, переход на этот адрес вызывает такое же завершение программы как и выполнение команды INT 20H непосредственно в тексте программы, но с одним важным отличием: выполняя переход на адрес PSP:0000H, программа устанавливает регистр CS на начало PSP, а это и есть единственное ограничение, нак- ладываемое на выполнение прерывания по завершению программы. В описа- нии функции 4CH системы MS-DOS приводится пример того, как .EXE-прог- рамма может завершиться с помощью PSP:0000H. Программа в примере сна- чала спрашивает у системы MS-DOS номер ее версии, а потом завершается с помощью PSP:0000H для версий младших, чем 2.0. Программы, работающие под управлением MS-DOS 2.0 и более поздних версий, также могут исполь- зовать поле PSP:0000H; в примере программа использует функцию 4СН только потому, что она предпочтительнее в более поздних версиях MS-DOS. 1.2.4. Команда RET Другим популярным методом завершения программ в системе CP/M яв- ляется простое выполнение команды RET. Такой способ срабатывает по той причине, что система CP/M, прежде чем передать управление программе, заносит адрес вектора перезапуска из памяти в стек. Система MS-DOS обеспечивает такое средство лишь для .COM-программ; она НЕ заносит ад- рес завершения в стек, прежде чем передать управление .EXE-программе. Если программист хочет использовать команду RET для возврата в MS-DOS, он может воспользоваться вариантом программы из Рис.4-3 , при- веден- ным на Рис.4-4 . _____________________________________________________________________ TEXT SEGMENT PARA PUBLIC 'CODE' ASSUME CS:TEXT,DS:NOTHING,ES:NOTHING,SS:NOTHING ENTRY_PROC PROC FAR ;в процедуре FAR будет удаленная команда FAR ;занести в стек указатель на вектор завершения в PSP PUSH DS ;занести в стек адрес сегмента PSP XOR AX,AX ;в АХ - смещение 0 в PSP вектора перезапуска PUSH AX ;занести в стек смещение в PSP вектора пере- ;запуска ;* * * * * Здесь расположите основную программу * * * * * ;Определить версию MS-DOS, обойти завершение для версий, более ранних, ;чем 2.0 MOV AH,30h ;загрузить код функции определения версии MS-DOS INT 21h ;обратиться к MS-DOS для получения номера версии OR AL,AL ;версия более ранняя, чем 2.0 ? JNZ TERM_0200 ;нет ;Завершение для версии 2.0 MS-DOS и более ранних RET ;выбрать из стека значения PSP:00H в CS:IP для завершения ;Terminate under MS-DOS 2.0 or later TERM_0200: MOV AX,4C00h ;загрузить код функции завершения и код заверше- ;ния INT 21h ;обратиться к MS-DOS для завершения программы ENTRY_PROC ENDP TEXT ENDS END ENTRY_PROC ;определить точку входа Рис. 4-4. Использование команды RET для возврата в MS-DOS. _____________________________________________________________________ 1.2.5. Функция завершения процесса Последний способ завершения .EXE-программы - прерывание 21Р функ- ции 00Н (Завершить процесс). Этот метод имеет то же самое ограничение, что и все предыдущие: регистр CS должен указывать на начало PSP. Из-за этого ограничения .EXE-программы обычно избегают пользоваться этим способом завершения, а предпочитают завершаться через PSP:0000H, как описывалось раньше для программ, работающих под управлением ранних версий системы MS-DOS. 1.2.6. Завершиться и остаться резидентной .EXE-программа может воспользоваться любым из нескольких дополни- тельных методов завершения для того, чтобы передать управление системе MS-DOS, а самой остаться размещенной в памяти до выполнения некоторого специального события. Смотри руководство ПРОГРАММИРОВАНИЕ В СРЕДЕ MS-DOS: Руководство пользователя: Утилиты завершения программы и сох- ранения ее в памяти. 1.3. Структура .EXE-файлов До сих пор мы касались вопросов, как представляются .EXE-програм- мы в памяти, как система MS-DOS передает управление программе , как программы могут вернуть управление MS-DOS. Теперь рассмотрим, что представляет собой программа как дисковый файл, прежде чем система MS-DOS загрузит ее в память. На рис. 4-5 приведена общая структура .EXE-файла. 1.3.1. Заголовок файла В отличие от файлов .COM-программ, файлы .EXE-программ содержат информацию, позволяющую .EXE-программе и системе MS-DOS использовать все разнообразие возможностей, предоставляемых микропроцессорами се- мейства 8086. Редактор связей помещает всю дополнительную информацию в заголовок, расположенный в начале .EXE-файла. Хотя структура .EXE-фай- ла позволяет легко выделить для заголовка 32 байта, редактор никогда не строит его меньше, чем 512 байтов. (Такой размер заголовка соответ- ствует длине стандартной записи системы MS-DOS.) Заголовок .EXE-файла содержит следующую информацию, которую MS-DOS считывает во временную рабочую область в память и использует при загрузке .EXE-программы. 00-01Н (.EXE-тип). Система MS-DOS не обращается к расширению (.EXE или .COM), когда хочет определить, .EXE- или .COM-программа на- ходится в файле. MS-DOS считает, что в файле находится .EXE-программа, если в первых двух байтах заголовка содержится код 4DH 5AH (что соот- ветствует символам M и Z кода ASCII). Если же оба или хотя бы один байт типа содержит другие значения, MS-DOS считает, что в файле нахо- дится .COM-программа, независимо от того, каким было расширение. Об- ратное, вообще говоря, не всегда верно: MS-DOS не считает, что файл является .EXE-программой только потому, что код типа соответствует .EXE-программе. Файл должен удовлетворять еще некоторым условиям. 02-03Н (Размер последней страницы). Слово по этому адресу указы- вает реальное количество байтов в последней 512-байтной записи файла. Это слово вместе со следующим за ним определяют реальную длину файла. 04-05Н (Страницы файла). Это слово содержит счетчик общего числа 512-байтных страниц, необходимых для размещения всего файла. Если файл имеет длину 1024 байта, в этом слове находится значение 0002Н: если файл иммет длину 1025 байтов, в этом слове находится значение 0003Н. Предыдущее слово (Размер последней страницы, 02-03Н) используются для определения реального количества байтов в последней 512-байтной стра- нице. Следовательно, если файл содержит 1024 байта, поле "Размер стра- ницы" имеет значение 0000Н, потому что ни один байт не попадает в пос- леднюю, частично используемую страницу; если файл содержит 1025 бай- тов, поле "Размер последней страницы" имеет значение 0001Н, потому что последняя страница содержит лишь один значимый байт (1025-ый байт). 06-07Н (Адрес таблицы перемещений). Это слово содержит количество входов в таблице указателей перемещений. Смотри ниже раздел "Таблица указателей перемещений". 08-09Н (Параграфы заголовка). Это слово содержит размер заголовка --------T-------T-------T-------T-------T-------T-------T-------¬ ¦ Тип ¦Размер ¦Кол-во ¦Адрес ¦Пара- ¦MIN ¦MAX ¦Начал. ¦ ¦програ-¦послед.¦страниц¦таблицы¦графы ¦ALLOC ¦ALLOC ¦знач. ¦ ¦ммы ¦страни-¦файла ¦переме-¦заго- ¦ ¦ ¦регист-¦ 0xH ¦ ¦цы ¦ ¦щений ¦ловка ¦ ¦ ¦ра SS ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦4DH¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦ ¦ ¦5AH¦byt¦byt¦byt¦byt¦byt¦byt¦byt¦byt¦byt¦byt¦byt¦byt¦byt¦byt¦ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ¦Начал. ¦Отриц. ¦Начал. ¦Первич.¦Смещен.¦Кол-во ¦ ¦ ¦знач. ¦контр. ¦знач. ¦смещен.¦таблицы¦овер- ¦ ¦ ¦регист-¦сумма ¦регист-¦регист-¦наст- ¦лейных ¦Зарезервировано¦ 1xH ¦ра SP ¦ ¦ра IP ¦ра CS ¦ройки ¦сегмен.¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ofs¦ofs¦lo ¦hi ¦ofs¦ofs¦seg¦seg¦lo ¦hi ¦lo ¦hi ¦ ¦ ¦lo ¦hi ¦byt¦byt¦lo ¦hi ¦lo ¦hi ¦byt¦byt¦byt¦byt¦ ¦ +---+---+---+---+---+---+---+---+---+---+---+---+ ¦ ............................................................... +---------------T---------------T---------------T---------------+ ¦Указатель адре-¦Указатель адре-¦Указатель адре-¦Указатель адре-¦ ¦са настройки #1¦са настройки #2¦са настройки #3¦са настройки #4¦ ¦ ¦ ¦ ¦ ¦ ¦ofs¦ofs¦seg¦seg¦ofs¦ofs¦seg¦seg¦ofs¦ofs¦seg¦seg¦ofs¦ofs¦seg¦seg¦ ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ............................................................... Используйте смещение таблицы настройки по смещению 18H (смещение от начала файла) +---------------T---------------T---------------T---------------+ ¦Указатель адре-¦Указатель адре-¦Указатель адре-¦Указатель адре-¦ ¦са настройки ¦са настройки ¦са настройки ¦са настройки #n¦ ¦#n-3 ¦#n-2 ¦#n-1 ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ofs¦ofs¦seg¦seg¦ofs¦ofs¦seg¦seg¦ofs¦ofs¦seg¦seg¦ofs¦ofs¦seg¦seg¦ ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦lo ¦hi ¦ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ .............................................................. Используйте поле "количество адресов настройки" по смещению 06 +--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---+ ¦ ¦ +---------------------------------------------------------------+ ¦ Образ программы ¦ +--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---+ ¦(загрузочный модуль) Используйте поле Последняя 512-байтная¦ ¦ "размер последней страница, как указано¦ ¦ страницы" по сме- в поле "страницы фай-¦ ¦ щению 02H ла" по смещению 04H ¦ +---------------------------------------------------------------+ | | L--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---- Используйте поле "число параграфов заголовка" по смещению 08H Рис. 4-5. Структура EXE-файла. .EXE-файла в параграфах длиной по 16 байтов. Оно указывает смещение компилированного/ассемблированного и редактированного образа программы (загрузочного модуля) внутри .EXE-файла. Если вычесть это слово из двух слов, содержащих длину файла и располагающихся со смещениями 02Н и 04Н, получим длину образа программы. Заголовок всегда превышает и даже занимает несколько 16-байтных параграфов. Например, если файл состоит из заголовка длиной 512 байтов и образа программы длиной 513 байтов, тогда общая длина файла равна 1025 байтов. Как указывалось вы- ше, поле "Размер последней страницы" длиной в слово (02-03Н) будет иметь значение 0001Н, а поле "Страницы файла" (04-05) - 0003Н. Так как длина заголовка равна 512 байтов, поле "Параграфы заголовка" (08-09Н) будут иметь значение 32 (0020Н). (Если 32 параграфа умножить на 16 байтов/параграфов получим 512 байтов.) Если вычесть 512 байтов заго- ловка из общей длины файла, равной 1025 байтов, можно определить длину образа программы, в конкретном примере 513 байтов. 0А-0ВH (MINALLOC - минимальное распределение памяти). В этом сло- ве указывается минимальное количество 16-байтных параграфов, которые необходимо выделить программе, прежде чем начать ее выполнение в д_о_п_о_л_н_е_н_и_е к памяти, выделенной для размещения образа прог- раммы. Поле MINALLOC обычно отражает общую длину всех неинициализиро- ванных данных и/или стековых сегментов, присоединенных в конце прог- рамм. Редактор отбирает память, занятую этими переменными в конце .EXE-файла, чтобы не терять напрасно дисковое пространство. Если при загрузке программы не хватает памяти, чтобы удовлетворить требование MINALLOC, система MS-DOS возвращает код ошибки процессу, пытавшемуся загрузить программу. Если это был процесс COMMAND.COM, он выводит на экран сообщение об ошибке: "Программа слишком велика. Не может размес- титься в памяти" (Program too big to fit in memory). Утилита EXEMOD может изменить поле MINALLOC в случае необходимости. Смотри раздел "Изменение заголовка .EXE-файла" ниже. 0С-0DH (MAXALLOC - Максимальное распределение памяти). В этом слове указывается максимальное количество 16-байтных параграфов, кото- рые программа хотела бы получить, прежде чем начать свою работу. Поле MAXALLOC указывает размер дополнительной памяти, выделяемой сверх той, которая нужна для размещения образа программы. Система MS-DOS исполь- зует это значение для выделения дополнительных параграфов, если они имеются в наличии. Если таковые отсутствуют, программа получает наи- больший доступный блок памяти - по крайней мере для дополнительных па- раграфов в количестве, указанном в поле MINALLOC. Программист может использовать поле MAXALLOC для того, чтобы запросить у системы MS-DOS память, например, под буфер печати или динамическую область программы. Если в процессе редактирования это поле не было специфицировано с помощью ключа/CPARMAXALLOC, редактор присваивает ему значение FFFFH. Это вынуждает систему MS-DOS выделить программе наибольший доступный блок свободной памяти. Чтобы сделать программу совместимой с многоза- дачными программами Супервизора, програмист должен использовать перек- лючатель /CPARMAXALLOC, чтобы задать реальное максимальное число до- полнительных параграфов, необходимых программе. Чтобы изменить данное поле, можно также воспользоваться утилитой EXEMOD. З_а_м_е_ч_а_н_и_е. Если оба поля MINALLOC и MAXALLOC получили значение 0000Н, MS-DOS загрузит программу в память как можно выше. Ре- дактор LINK присваивает этим полям значение 0000Н, если был задан ключ /HIGH; чтобы изменить значения этих полей, можно использовать утилиту EXEMOD. 0E-0F (Начальное значение регистра SS). Это слово содержит адрес параграфа стекового сегмента относительно начала загрузочного модуля. В процессе загрузки система MS-DOS прибавляет к этому значению адрес начального сегмента программы, и полученное значение помещает в ре- гистр SS, прежде чем передать управление программе. (Начальный сегмент - это первый сегмент в памяти, следующий за PSP.) 10-11H (Начальное значение регистра SP). В этом слове содержится абсолютное значение, которое MS-DOS загружает в регистр SP, прежде чем передать управление программе. Так как система MS-DOS всегда загружает программы, начиная с границы сегмента, а редактор знает размер стеко- вого сегмента, сам редактор и может в процессе своей работы определить правильное значение смещения SP; таким образом, системе MS-DOS уже нет необходимости определять это значение в ходе загрузки. Для модификации этого поля можно также использовать утилиту EXEMOD. 12-13Н (Дополнительная контрольная сумма) Это слово содержит до- полнение суммы все слов .EXE-файла. Существущие версии системы MS-DOS игнорируют содержимое этого слова при загрузке .EXE-программы; но, возможно, последующие версии будут его анализировать. Когда редактор порождает .EXE-файл, он параллельно суммирует все его содержимое (включая заголовок), рассматривая весь файл как длинную последователь- ность 16-разрядных слов. В ходе этого суммирования редактор LINK прис- ваивает полю "Дополнительная контрольная сумма" (12-13Н) временное значение 0000Н. Если файл состоит из нечетного количества байтов, пос- ледний байт трактуется как слово со старшим байтом, равным 00Н. Как только редактор суммировал все слова .EXE-файла, он выполняет операцию дополнения до единицы над полученной суммой и заносит результат в за- головок .EXE-файла в поле со смещением 12 - 13Н. Впоследствии можно проверить правильность .EXE-файла, выполнив те же самые операции, ко- торые проделал редактор. Сумма всех слов должна быть равна FFFFH, по- тому что она включает и вычисленную редактором дополнительную конт- рольную сумму, разработанную специально таким образом, чтобы сумма слов файла была равна FFFFH. На примере .EXE-файла длиной 7 байтов можно проиллюстрировать, как вычисляются контрольные суммы. (Этот файл совершенно фиктивный, потому что заголовок реального .EXE-файла никогда не бывает меньше 512 байтов .) Если в этом фиктивном файле содержатся байты 8СН С8Н 8EН D8Н BAH 10Н В4Н, то его сумма будет вычисляться следующим образом: С88СН + D88EH + + 10ВАН + 00В4Н = 1В288Н. (Переполнение свыше 16 байтов игно- рируется, поэтому получаем величину В288Н.) Если бы это был реальный файл, тогда вместо FFFFH была бы занесена сумма В288Н. 14-15Н (Начальное значение регистра IP). В этом слове находится абсолютное значение, которое система MS-DOS загружает в регистр IP для передачи управления программе. Так как MS-DOS всегда загружает прог- раммы, начиная с границы сегмента, редактор может определить правиль- ное смещение регистра IP на основе значения регистра CS в ходе редак- тирования; тогда системе MS-DOS не потребуется вычислять это значение во время загрузки программы. 16-17Н (Предварительное начальное значение регистра CS). В этом слове содержится начальное значение, вычисленное относительно начала загрузочного модуля, которое система MS-DOS помещает в регистр CS при передаче управления .EXE-программе. MS-DOS определяет это значение по- добно тому, как и начальное значение регистра SS, прежде чем загрузить его в регистр CS. 18-19Н (Смещение таблицы перемещений). В этом слове находится смещение относительно начала файла таблицы указателей пмеремещений. Это слово должно использоваться при определении местонахождения табли- цы указателей перемещений, потому что перед таблицей может распола- гаться информация переменной длины, касающаяся программных оверлейных сегментов, и позиция таблицы может изменяться. 1А-1ВН (Количество оверлейных сегментов). Это слово обычно содер- жит значение 0000Н, которое говорит о том, что .EXE-файл состоит толь- ко из резидентной, или основной, части программы. Это число изменяется только для файлов, содержащих программы с оверлейными сегментами, ко- торые представляют собой отдельные секции программы, находящиеся на диске до тех пор, пока программа не затребует их. Эти программные сек- ции загружаются в память с помощью специальных программ управления оверлейными структурами, находящихся в библиотеках реального времени, поставляемых фирмой Microsoft вместе с компиляторами языков высокого уровня. Заголовку предшествует секция (00 - 1ВН), известная под названием "форматированная область". Вслед за этой областью может располагаться информация, необходимая программам управления оверлейными структурами для языков высокого уровня. Если программа в .EXE-файле не содержит такой информации, за форматированной областью заголовка сразу следует таблица указателей перемещений. Т_а_б_л_и_ц_а у_к_а_з_а_т_е_л_е_й п_е_р_е_м_е_щ_е_н_и_й.Таблица указателей перемещений представляет собой список указателей на слова, расположенные внутри образа .EXE-программы, которые система MS-DOS должна сформировать, прежде чем передать управление программе. Эти слова представляют собой ссылки на сегменты внутри программы. MS-DOS должна сформировать эти адресные ссылки на сегменты во время загрузки программы, потому что програма может быть загружена в память, начиная с любого адреса сегмента. Каждый указатель таблицы представляет собой двойное слово. Первое слово является смещением от адреса сегмента, заданного вторым словом, которое, в свою очередь, является адресом сегмента относительно начала загрузочного модуля. Вместе эти два слова указывают на третье слово в тексте загрузочного модуля, к содержимому которого необходимо приба- вить адрес стартового сегмента. (Стартовый сегмент - это адрес сегмен- та, с которого MS-DOS начинает загружать образ программы.) На рис. 4-6 показана вся процедура, которую выполняет система MS-DOS для каждого входа таблицы перемещений. 1.3.2. Загрузочный модуль Загрузочный модуль начинается там, где заканчивается .EXE-заголо- вок, и представляет собой единый связный образ программы. Загрузочный модуль в .EXE-файле имеет точно такой же вид, как если бы он был заг- ружен системой MS-DOS в память, начиная с адреса сегмента 0000Н. Един- ственные изменения, которые вносит MS-DOS в загрузочный модуль, каса- ются определения всех прямых адресов сегментов. Несмотря на то, что загрузочный модуль в .EXE-файле состоит из отдельных сегментов, никакой информации об их границах не имеется. Су- ществующие версии MS-DOS не интересуются тем, как сегментирована прог- рамма; они просто копируют загрузочный модуль в память, определяют все прямые адреса сегментов и передают управление программе. .EXE-файл ---------------------¬ Конец файла ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ -----------------¬ ¦ ¦ ¦ Отн.адрес ¦ ¦ ¦ ¦ сегмента=003CH ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ Абс.адрес ¦ ¦ ¦ ¦ сегмента=25D1H ¦ ¦ ¦ L----------------- ¦ ¦ Загрузочный ¦ ¦ модуль ¦ +--------------------+ ¦ ¦ ¦ ¦ . . . . . . . . . . . Память ¦ ¦ ------------------¬ ¦ ¦ ¦ ¦ ¦ -----------------¬ ¦ 003CHў--¬ ¦ ---------------¬¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ Отн.адрес ¦¦ ¦ ¦ Указатель ¦ ¦ -Ў+2595H L----сегмента=003CH¦¦ ¦ ¦ перемещений ¦ ¦ ¦ ----- ¦ ¦ ¦¦ ¦ ¦ 0002H:0005H ¦ +-¬ ¦ 25D1H-------Ў Абс.адрес ¦¦ ¦ L----------------- ¦ ¦ ¦ ¦ ¦сегмента=25D1H¦¦ ¦ ¦ ¦ ¦ ------------ЎL---------------¦ ¦ Таблица указателей ¦ L--0022H:0005H ¦ ¦ ¦ Загрузочный ¦ ¦ перемещений ¦ +2595H ў----+ ("Стартовый¦ модуль ¦ +--------------------+ ----------- ¦ сегмент"Ў+-----------------+ ¦ Форматированная ¦ 2597H:0005H---- 2595H ¦ Префикс сегмента¦ ¦ область заголовка ¦ ¦ программы ¦ L--------------------- L------------------ Начало файла Рис. 4-6. Процедура модификации адресов .EXE-файла. 1.4. Загрузка .EXE-программы Выше мы рассмотрели все особенности .EXE-программы, касающиеся ее размещения в памяти и на диске. Мы также затронули все те действия, которые выполняет операционная система MS-DOS, загружая программу с диска и выполняя ее. Следующий список операций описывает процесс заг- рузки .EXE-программы в той последовательности, в которой MS-DOS выпол- няет его. 1. Система MS-DOS считывает форматированную область заголовка (первые 1ВН байтов) из .EXE-файла в рабочую область. 2. MS-DOS определяет размер наибольшего доступного блока памяти. 3. MS-DOS определяет длину загрузочного модуля с помощью значений полей "Размер последней страницы" (смещение 02Н), "Страницы файла" (смещение 04Н), "Параграфы заголовка" (смещение 08Н), находящихся в заголовке. Пример такого вычисления приведен в описании поля "Параграфы заголовка". 4. MS-DOS добавляет значение поля MINALLOC (смещение 0АН) заго- ловка к вычисленной длине загрузочного модуля и длине PSP (100Н байтов). Если полученная сумма превышает размер наиболь- шего доступного блока памяти, MS-DOS завершает процесс загруз- ки и возвращает код ошибки вызывавшему процессу. Если вызывав- шим был процесс COMMAND.COM, он выведет на экран сообщение об ошибке: "Программа слишком велика. Не может разместиться в па- мяти". 5. MS-DOS добавляет значение поля MAXALLOC (смещение 0СН) заго- ловка к вычисленной длине загрузочного модуля и длине PSP. Ес- ли размер найденного ранее блока памяти превышает полученную сумму, MS-DOS выделяет программе затребованный объем памяти из этого блока; если полученная сумма превышает размер блока, MS-DOS выделяет программе весь блок памяти. 6. Если оба поля MINALLOC и MAXALLOC содержат значение 0000Н, MS-DOS использует вычисленный размер загрузочного модуля для определения стартового сегмента. MS-DOS находит стартовый сег- мент таким образом, чтобы загрузочный модуль был помещен в са- мый конец выделенного блока памяти. Если же наоборот, поля MINALLOC и MAXALLOC содержат ненулевые значения (обычный слу- чай), в качестве стартового сегмента система принимает сег- мент, следующий за PSP. 7. MS-DOS загружает загрузочный модуль в память, начиная со стар- тового сегмента. 8. MS-DOS считывает указатели перемещений в рабочую область и оп- ределяет прямые адреса сегментов загрузочного модуля так, как показано на рис. 4-6. 9. MS-DOS строит PSP в первых 100Н байтах выделенного блока памя- ти. При построении двух FCB в рамках PSP, MS-DOS определяет начальные значения регистров AL и AH. 10.MS-DOS устанавливает значение регистров SS и SP в соответствии с полями заголовка после того, как адрес стартового сегмента прибавлен к содержимому регистра SS. 11.MS-DOS устанавливает регистры DS и ES на начало PSP. 12.MS-DOS передает управление .EXE-программе, устанавливая регис- тры CS и IP согласно соответствующим полям заголовка после то- го, как адрес стартового сегмента прибавлен к содержимому ре- гистра CS. 1.5. Управление структурой .EXE-программы Мы рассмотрели почти все вопросы, касающиеся уже готовой .EXE- программы. Далее посмотрим, как можно управлять структурой готовой .EXE-программы на уровне исходного текста. Начнем с изучения операто- ров MASM, позволяющих определять структуру будущей программы, програм- мируя на языке ассемблера. Далее рассмотрим пять стандартных моделей памяти, поддерживаемых компиляторами фирмы Microcoft с языков Си и FORTRAN (оба версий 4.0), которые обеспечивают предварительное струк- турирование программы и которыми программист может частично управлять. 1.5.1. Директива макроассемблера SEGMENT Директива SEGMENT и связанная с ней директива END означают начало и конец программного сегмента. Программные сегменты содержат программ- ный код или данные, которые имеют адресные смещения относительно одно- го общего сегмента. Кроме обязательного имени сегмента директива SEGMENT имеет три необязательных параметра: имя-сегмента SEGMENT [align][combine]['class'] С помощью MASM содержимое сегмента может быть определено в одной точке исходного файла, а затем , в случае необходимости, в других час- тях файла. Когда макроассемблер встречает директиву SEGMENT с уже ра- нее появлявшимся именем сегмента, он просто продолжает уже имеющееся определение сегмента. Это происходит независимо от параметра combine, специфицированного в директиве SEGMENT; объединительный тип (combine) влияет только на действия редактора. Смотри ниже раздел "Параметр типа combine". 1.5.1.1. Параметр типа align Необязательный параметр align дает возможность программисту пере- дать редактору указание, как выравнять сегмент в памяти. На самом деле редактор может выравнять сегмент относительно начала загрузочного мо- дуля программы, но результат всегда остается одним и тем же, потому что система MS-DOS всегда загружает модуль, начиная с границы парагра- фа (16 байт ). (Особым исключением является тип PAGE параметра align, который будет рассмотрен ниже.) Допускаются следующие типы выравниваний. BYTE. Этот тип парметра align заставляет редактор разместить сег- мент, начиная с байта, следующего непосредственно за пердыдущим сег- ментом. Выравнивание типа BYTE исключает потери памяти между предыду- щим сегментам и сегментом, выровненным с помощью типа BYTE. Небольшим недостатком выравнивания типа BYTE является то, что ре- гистры микропроцессоров семейства 8086 не всегда смогут прямо адресо- ваться к началу сегмента. Так как они могут адресоваться только на границу параграфа, сегментные регистры могут указывать лишь на 15 бай- тов за началом сегмента. Это означает, что размер сегмента не может превышать 15 полуслов из 64 Кбайт. Редактор определяет смещения и ука- затели на адреса сегментов, чтобы компенсировать разницу между физи- ческим началом сегмента и границей адресации параграфа. Другим важным фактором является скорость выполнения операций на 16-разрядных микропроцессорах семейства 8086. При использовании других микропроцессоров программа может выполняться быстрее, если команды и данные длиной в слово внутри сегментов выравнены на границу слова. Это позволяет 16-разрядному процессору осуществлять выборку полного слова за одно обращение к памяти, а не выполнять две операции чтения по од- ному байту. Директива EVEN заставляет макроассемблер выравнивать ко- манды и поля данных на границу слова; однако MASM может выполнить та- кое выравнивание только относительно начала сегмента; таким образом начало сегмента должно быть выравнено на границу слова или еще большей величины, чтобы гарантировать выравнивание всех элементов внутри сег- мента. WORD. Этот тип параметра align требует, чтобы редактор разместил сегмент, начиная с границы следующего слова. Границы слов всречаются через каждые 2 байта и имеют только четные адреса (адреса, в которых самый младший значащий бит равен нулю). Значение параметра WORD позво- ляет выравнивать поля данных и команды внутри сегмента на границы слов, как объяснялось для типа выравнивания BYTE. Однако может слу- читься так, что редактору придется пропустить один байт памяти между предыдущим сегментом и сегментом, выравненным на границу слова, чтобы правильно позиционировать новый сегмент. Другой небольшой недостаток выравнивания на границу слова для микропроцессоров семейства 8086 состоит в том, что сегментные регистры не во всех случаях смогут напрямую адресоваться к началу сегмента. Так как они могут адресоваться только на границы параграфов, сегментные регистры могут указывать лишь на 14 байтов за началом сегмента. Это означает, что размер сегмента не должен превышать 64 Кбайт минус 14 байтов. Редактор определяет смещения и указатели на адреса сегментов, чтобы компенсировать разницу между физическим началом сегмента и гра- ницей адресации параграфа. PARA. Этот тип параметра align требует , чтобы редактор разместил сегмент, начиная с границы следующего параграфа. Если тип выравнивания не задан, сегмент по умолчанию выравнивается на границу параграфа.Гра- ницы параграфов встречаются через каждые 16 байтов и состоят из адре- сов, шестнадцатиричные значения которых заканчиваются нулями (0000Н, 0010Н, 0020Н, и т.п.). Выравнивание на границу параграфа гарантирует, что сегмент начинается на границе адресации сегментного регистра, что дает возможность адресовать весь сегмент длиной 64 Кбайт. Кроме того, поскольку адреса параграфов - четные, выравнивание типа PARA имеет те же преимущества, что и выравнивание типа WORD. Единственным недостат- ком выравнивания типа PARA является то, что редактор может опустить как ненужные 15 байтов памяти между предыдущим сегментом и сегментом, выравненным на границу параграфа. PAGE. Этот тип параметра align требует, чтобы редактор разместил сегмент, начиная со следующей страницы. Границы страниц появляются че- рез каждые 256 байтов и имеют адреса, в которых младший байт равен ну- лю (0000Н, 0100Н, 0200Н и т.п.). Выравнивание PAGE гарантирует только, что редактор разместит сегмент на границе страницы относительно начала загрузочного модуля. К сожалению это никак не гарантирует выравнивание сегмента на абсолютную страницу всей памяти, так как система MS-DOS обеспечивает выравнивание всего загрузочного модуля лишь на границу параграфа. Если программист объявляет части сегмента с одним и тем же именем в различных исходных модулях, тип параметра align, заданный для каждой части сегмента, влияет на выравнивание именно этой специфической части сегмента. Например, пусть следующие два описания сегмента встречаются в различных исходных модулях: _DATA SEGMENT PARA PUBLIC 'DATA' DB '123' _DATA ENDS _DATA SEGMENT PARA PUBLIC 'DATA' DB '456' _DATA ENDS Редактор начинает с того, что выравнивает первую часть сегмента, находящуюся в первом объектном модуле, на границу параграфа, как это и требовалось. Как только редактор обнаруживает вторую часть сегмента во втором объектном модуле, он выравнивает эту часть на границу параграфа следом за первой частью сегмента. В результате между первой и второй частями сегмента образуется зазор в 13 байтов. Это происходит только в том случае, если части сегмента располагаются в разных исходных моду- лях. Если приведенные части сегмента находятся в одном и том же исход- ном модуле, макроассемблер считает, что второе объявление сегмента - это только повторение первого, и создает объектный модуль со следующим описанием сегмента: _DATA SEGMENT PARA PUBLIC 'DATA' DB '123' DB '456' _DATA ENDS 1.5.1.2. Параметр combine Необязательный параметр combine позволяет передать редактору ука- зания о том, как комбинировать сегменты с одним и тем же именем сег- мента, встречающиеся в разных объектных модулях, Если не задано ника- кое значение параметра combine, редактор воспринимает указанные сег- менты как сегменты с разными именами. Параметр combine не может влиять на взаимосвязи сегментов с разными именами. Макроассемблер и редактор LINK обрабытывают следующие значения параметра combine. PUBLIC. Этот тип параметра combine указывает редактору, что необ- ходимо конкатенировать различные сегменты с одним и тем же именем в один непрерывный сегмент. Редактор изменяет все адресные ссылки на метки в пределах конкатенированных сегментов, чтобы отразить новое по- ложение этих меток относительно начала комбинированного сегмента. Та- кой тип параметра combine удобно использовать для доступа к программам и данным, находящимся в различных исходных модулях, используя один об- щий сегментный регистр. STACK. Этот тип параметра combine работает почти аналогично типу PUBLIC, но с двумя дополнительными особенностями. Тип параметра STACK указывает редактору, что данный сегмент содержит часть программного стека, и инициализируемые данные, находящиеся в пределах стековых сег- ментов, обрабатываются непосредственного в сегментах типа PUBLIC. Объ- явление сегментов типа STACK с помощью параметра combine позволяет ре- дактору определить начальные значения регистров SS и SP, которые он помещает в заголовок .EXE-файла. Как правило, программист объявляет только один сегмент типа STACK в одном из исходных модулей. Если части стека появляются в различных исходных модулях, редактор конкатенирует их аналогично тому, как он это проделывает с сегментами типа PUBLIC. Однако инициализируемые данные, определенные в рамках любого сегмента типа STACK, будут размещены в самом верхнем конце объединенных сегмен- тов STACK по принципу модуль-за-модулем. Таким образом, все последую- щие инициализируемые данные модулей перекрывают данные предыдущих мо- дулей. По крайнкй мере один сегмент должен быть определен как имеющий тип STACK с помощью параметра combine; в противном случае редактор вы- даст предупреждение, потому что не сможет определить начальные значе- ния регистров SS и SP. (Предупреждение можно игнорировать, если прог- рамма сама инициализирует регистры SS и SP.) COMMON. Этот тип параметра combine указывает редактору нак- лады- вать друг на друга сегменты с одним и тем же именем. Длина резуль- ти- рующего сегмента будет соответствовать длине самого длинного объяв- ленного сегмента. Если внутри перекрывающихся сегментов находится программа или данные, то находящиеся в последних присоединенных сег- ментах данные вытесняют все данные ранее загруженных сегментов. Такой тип параметра combine удобен в тех случаях, когда область данных раз- деляется программами различных исходных модулей. MEMORY. Редактор LINK фирмы Microsoft обрабатывает этот тип пара- метра combine аналогично типу PUBLIC. Однако MASM поддерживает тип MEMORY для совместимости с другими редакторами, которые воспринимают тип MEMORY согласно определению фирмы Intel. AT адрес. Этот тип параметра combine заставляет редактор LINK "притвориться", что вроде бы сегмент будет размещен по абсолютному ад- ресу. В таком случае LINK настроит все адресные ссылки сегмента в со- ответствии с принятым сценарием. Редактор LINK _н_е_ построит образ сегмента в загрузочном модуле и проигнорирует все данные, определенные внутри сегмента. Такие действия редактора вполне согласуются с тем фактом, что система MS-DOS не обеспечивает загрузку программных сег- ментов в абсолютные сегменты памяти. Все программы должны уметь выпол- няться, начиная с любого сегментного адреса, по которому система MS-DOS обнаружила свободную память. Тип параметра combine 'ATадрес' оператора SEGMENT удобен для создания шаблонов различных областей па- мяти вне программы. Например, оператор SEGMENT AT 0000H может исполь- зоваться для создания шаблона вектора прерывания для семейства микроп- роцессоров 8086. Так как данные, находящиеся внутри сегментов, опреде- ленных с помощью оператора 'SEGMENT AT адрес' подавляются редактором LINK, а не MASM (который помещает данные в объектный модуль), можно использовать .OBJ-файлы, построенные MASM, c другим редактором, кото- рый обеспечивает работу с ПЗУ или другими средствами с абсолютными ад- ресами, в том случае, если программисту необходима эта специфическая возможность. 1.5.1.3. Параметр class Параметр class дает возможность объединять различные сегменты в классы. Например, имееется три исходных модуля, каждый из которых со- держит свои программные сегменты и сегменты данных: ;Модуль "A" A_DATA SEGMENT PARA PUBLIC 'DATA' ;поля данных модуля "A" A_DATA ENDS A_CODE SEGMENT PARA PUBLIC 'CODE' ;текст программы модуля "A" A_CODE ENDS END ;Mодуль "B" B_DATA SEGMENT PARA PUBLIC 'DATA' ;поля данных модуля "B" B_DATA ENDS B_CODE SEGMENT PARA PUBLIC 'CODE' ;текст программы модуля "B" B_CODE ENDS END ;Mодуль "C" C_DATA SEGMENT PARA PUBLIC 'DATA' ;поля данных модуля "C" C_DATA ENDS C_CODE SEGMENT PARA PUBLIC 'CODE' ;текст программы модуля "C" C_CODE ENDS END Если из операторов SEGMENT, приведенных выше, убрать тип 'CODE' и тип 'DATA' параметра class, редактор упорядочит сегменты по мере их поступления. Если программист передает модули редактору в алфавитном порядке, последний упорядочит сегменты следующим образом: A_DATA A_CODE B_DATA B_CODE C_DATA C_CODE Однако, если программист задает параметр class так, как показано в примере выше, редактор упорядочит сегменты по классам следующим об- разом: 'DATA' class: A_DATA B_DATA C_DATA 'CODE' class: A_CODE B_CODE C_CODE Обратите внимание на то, что редактор по-прежнему организует классы в том порядке, в котором поступают сегменты, принадлежащие раз- личным классам. Чтобы полностью управлять порядком организации сегмен- тов, программист может воспользоваться одним из трех методов. Наиболее предпочтительным является использование ключа /DOSSEG для редактора. Это обеспечивает такой порядок сегментов, который показан на рис. 4-1. Второй метод заключается в создании специального исходного модуля, со- держащего пустые блоки SEGMENT - ENDS для всех сегментов, описанных в других исходных модулях. Программист строит список сегментов в том по- рядке, в котором они должны появляться в памяти, а затем указывает .OBJ-файл для этого модуля, причем редактор должен будет обработать этот файл в первую очередь. Согласно такой процедуре порядок сегментов устанавливается прежде, чем редактор LINK начнет обрабатывать другие программные модули, поэтому программист может описывать сегменты в этих других модулях в любом удобном для него порядке. Например, следу- ющий исходный модуль переупорядочит результат предыдущего примера та- ким образом, что редактор разместит класс 'CODE' перед классом 'DATA'. A_CODE SEGMENT PARA PUBLIC 'CODE' A_CODE ENDS B_CODE SEGMENT PARA PUBLIC 'CODE' B_CODE ENDS C_CODE SEGMENT PARA PUBLIC 'CODE' C_CODE ENDS A_DATA SEGMENT PARA PUBLIC 'DATA' A_DATA ENDS B_DATA SEGMENT PARA PUBLIC 'DATA' B_DATA ENDS C_DATA SEGMENT PARA PUBLIC 'DATA' C_DATA ENDS END Вместо того, чтобы создавать новый модуль, третий метод помещает приведенный выше список упорядочения сегментов в начало первого моду- ля, содержащего реальный текст программы или данные. Аналогичный под- ход используется новыми компиляторами фирмы Microsoft, например, для языка Си, версия 4.0. Расположение сегментов внутри загрузочного модуля не вызывает из- менения адресных ссылок, находящихся внутри сегментов. Только параметр combine операторов GROUP и SEGMENT заставляет редактор выполнить пере- мещение адресов. Смотри ниже раздел "Директива GROUP макроассемблера". З_а_м_е_ч_а_н_и_е. Некоторые более старые версии Макроассемблера фирмы IBM заносят сегменты в объектный файл в алфавитном порядке, не- зависимо от их последовательности в исходном файле. Эти старые версии не позволяют управлять порядком расположения сегментов. Наилучшим ре- шением указанной проблемы представляется переход к новым версиям ас- семблера. 1.5.1.4. Переупорядочение сегментов с целью умень- шения размеров .EXE-файла Перекомпоновка сегментов может значительно уменьшить размер .EXE-программы на диске. Она выполняется следующим образом: сначала все неинициализированные поля данных помещаются в свои собственные сегменты, а затем редактору дается указание упорядочить программные сегменты так, чтобы все сегменты, содержащие неинициализируемые поля данных, были вынесены в конец программы. В процессе ассемблирования программных модулей MASM заносит в объектный модуль информацию для ре- дактора о всех инициализируемых и неинициализируемых областях сегмен- тов. В дальнейшем редактор использует эту информацию, чтобы не записы- вать неинициализируемые поля данных, расположенные в конце образа программы, в результирующий .EXE-файл. Чтобы знать, сколько памяти за- нимают эти поля, редактор устанавливает значение поля MINALLOC в заго- ловке .EXE-файла, которое и отражает размер области данных, не запи- санной в файл. Затем система MS-DOS воспользуется значением поля MINALLOC, чтобы восстановить опущенное пространство в процессе загруз- ки программы в память. 1.5.2. Директива GROUP макроассемблера Директива GROUP также может сильно влиять на представление .EXE-программы. Однако, директива GROUP не влияет на порядок следова- ния программных сегментов в памяти. Она скорее связывает программные сегменты в целях адресации. Директива GROUP имеет следующий синтаксис: имя_группы GROUP имя_сегмента,имя_сегмента,... Эта директива заставляет редактор переопределить все адресные ссылки в каждом из перечисленных сегментов относительно начала объяв- ленной группы. Начало группы определяется во время редактирования. Группа может начинаться с любого сегмента указанного списка, если ре- дактор поместит его в самую нижнюю часть памяти. Тот факт, что директива GROUP никогда не вызывает и не требует для размещения сегментов непрерывного участка памяти, дает интересные, хотя и не всегда желательные, возможности. Например, это позволяет программисту размещать сегменты, не принадлежащие объявленной группе, между сегментами, принадлежащими группе. Единственным ограничением, накладываемым на группу, является то, что последний байт последнего сегмента в группе должен размещаться в пределах 64 Кбайт от начала группы. На рис. 4-7 показан такой способ организации сегментов. П_р_е_д_у_п_р_е_ж_д_е_н_и_е. Типичная ошибка, касающаяся директи- вы GROUP, как правило, связана с оператором Макроассемблера OFFSET. Директива GROUP касается только адресных ссылок, порожденных прямыми адресными командами типа: MOV AX, FIELD LABEL но она не влияет на непосредственные адреса, порожденные командами ти- па: MOV AX, OFFSET FIELD LABEL Использовать оператор OFFSET для меток, находящихся внутри сег- ментов группы, можно следующим образом: MOV AX, OFFSET GROUP NAME:FIELD LABEL Програмист должен я_в_н_о запросить смещение от базового адреса группы, потому что макроассемблер в результате выполнения оператора OFFSET определяет смещение метки от начала её сегмента, а не группы. -------------------------------- ----------------------------------¬ • ¦ ¦ ¦ ¦ Сегмент С ¦ ¦ ¦ (в списке сегментов директивы ¦ ¦ -------------Метка C----Ў¦ GROUP) ¦ ¦ • +---------------------------------+ 64 Кбайт ¦ ------Метка B----Ў¦ ¦ максимум ¦ • ¦ Сегмент B ¦ ¦ ¦ ¦ ¦ (не указан в директиве ¦ ¦ ¦ Смещение к ¦ GROUP) ¦ ¦ Смещение метке В ¦ ¦ ¦ к метке С ° ¦ ¦ ¦ ¦ ----------------- +---------------------------------+ ¦ ¦ ------Метка А----Ў¦ ¦ ¦ ¦ • ¦ Сегмент A ¦ ¦ ¦ ¦ ¦ (в списке сегментов директивы ¦ ¦ ¦ Смещение к ¦ GROUP) ¦ ¦ ¦ метке А ¦ ¦ ¦ ¦ ¦ ¦ ¦ ° ° ° ¦ ¦ -------------------------------- L---------------------------------- Рис. 4-7. Сегменты одной группы, размещенные в памяти не последовательно друг за другом 1.5.3. Структурирование небольшой программы с по- мощью директив SEGMENT и GROUP Теперь, когда мы проанализировали функции, выполняемые директива- ми SEGMENT и GROUP, покажем, как они применяются для изменения струк- туры программы. Программа, изображенная на рис. 4-8, 4-9 и 4-10, сос- тоит из трех исходных модулей (MODULE_A, MODULE_B и MODULE_C), каждый из которых включает четыре следующие программные сегмента: ---------------------------------------------------------------- С е г м е н т О п р е д е л е н и е ---------------------------------------------------------------- _TEXT Текст программы или программный код _DATA Стандартный сегмент данных, содержащий предварительно проинициализированные по- ля данных, которые программа может изме- нять CONST Сегмент постоянных данных, содержащий поля констант, которые не изменяются программой _BSS Сегмент резервированной памяти, содер- (blosk storage жащий неинициализируемые поля данных segment) ---------------------------------------------------------------- --------------------------------------------------------------------- ;Исходный текст модуля MODULE_A ;Предписанный порядок расположения сегментов * * * * _TEXT SEGMENT BYTE PUBLIC 'CODE' _TEXT ENDS _DATA SEGMENT WORD PUBLIC 'DATA' _DATA ENDS CONTS SEGMENT WORD PUBLIC 'CONST' CONST ENDS _BSS SEGMENT WORD PUBLIC 'BSS' _BSS ENDS STACK SEGMENT PARA STACK 'STACK' STACK ENDS DGROUP GROUP DATA,CONST,BSS,STACK ;Определение констант * * * * * * * * * * * * * * * * * * * * * * * * CONST SEGMENT WORD PUBLIC 'CONST' CONST_FIELD_A DB 'Constant A' CONST ENDS ;Предварительно инициализируемые поля данных* * * * * * * * * * * * * * _DATA SEGMENT WORD PUBLIC 'DATA' DATA_FIELD_A DB 'Data A' _DATA ENDS ;Неинициализируемые поля данных* * * * * * * * * * * * * * * * * * * * _BSS SEGMENT WORD PUBLIC 'BSS' BSS_FIELD_A DB 5 DUP(?) _BSS ENDS ;Текст программы * * * * * * * * * * * * * * * * * * * * * * * * * * * _TEXT SEGMENT BYTE PUBLIC 'CODE' ASSUME CS:_TEXT,DS:DGROUP,ES:NOTHING,SS:NOTHING EXTRN PROC_B:NEAR EXTRN PROC_C:NEAR PROC_A PROC NEAR CALL PROC_B CALL PROC_C MOV AX,4C00H INT 21H PROC_A ENDP _TEXT ENDS ;Стек * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * STACK SEGMENT PARA STACK 'STACK' DW 128 DUP(?) STACK_BASE LABEL WORD STACK ENDS END PROC_A ---------------------------------------------------------------------- Рис. 4-8 Структурирование модуля A .EXE-программы ---------------------------------------------------------------------- ;Исходный текст модуля MODULE_B ;определение констант * * * * * * * * * * * * * * * * * * * * * * * * CONST SEGMENT WORD PUBLIC 'CONST' CONST_FIELD_B DB 'Constant D' ;константы модуля B CONST ENDS ;Предварительно инициализируемые поля данных* * * * * * * * * * * * * * _DATA SEGMENT WORD PUBLIC 'DATA' DATA_FIELD_B DB 'Data B' ;предварительно инициализи- ;руемые поля модуля В _DATA ENDS ;Неинициализируемые поля данных* * * * * * * * * * * * * * * * * * * * _BSS SEGMENT WORD PUBLIC 'BSS' BSS_FIELD_B DB 5 DUP(?) ;неинициализируемые поля мо- ;дуля В _BSS ENDS ;Текст программы* * * * * * * * * * * * * * * * * * * * * * * * * * * * DGROUP GROUP _DATA,CONST,_BSS _TEXT SEGMENT BYTE PUBLIC 'CODE' ASSUME CS:_TEXT,DS:DGROUP,ES:NOTHING,SS:NOTHING PUBLIC PROC_B ;ссылка в модуле A PROC_B PROC NEAR RET PROC_B ENDP _TEXT ENDS END ---------------------------------------------------------------------- Рис. 4-9. Структурирование модуля B .EXE-программы ---------------------------------------------------------------------- ;Исходный текст модуля MODULE_C ;Определение констант * * * * * * * * * * * * * * * * * * * * * * * * CONST SEGMENT WORD PUBLIC 'CONST' CONST_FIELD_C DB 'Constant C' ;константы модуля C CONST ENDS ;Предварительно инициализируемые поля данных* * * * * * * * * * * * * * _DATA SEGVENT WORD PUBLIC 'DATA' DATA_FIELD_C DB 'DATA C' ;предварительно инициализируе- ;мые поля модуля С _DATA ENDS ;Неинициализируемые поля данных * * * * * * * * * * * * * * * * * * * * _BSS_FIELD_C DB 5 DUP(?) ;неинициализируемые поля мо- ;дуля С _BSS ENDS ;Текст программы* * * * * * * * * * * * * * * * * * * * * * * * * * * * DGROUP GROUP _DATA,CONST,_BSS _TEXT SEGMENT BYTE PUBLIC 'CODE' ASSUME CS:_TEXT,DS:DGROUP,ES:NOTHING,SS:NOTHING PUBLIC PROC_C ;ссылка в модуле A PROC_C PROC NEAR RET PROC_C ENDP _TEXT ENDS END ---------------------------------------------------------------------- Рис. 4-10. Структурирование модуля C.EXE-программы В этом примере создается образ программы, моделирующей память так, что отредактированная программа будет иметь лишь один сегмент программного кода и один сегмент данных - простейшая стандартная форма .EXE-программы. Смотри ниже раздел "Использование современных моделей памяти фирмы Microsoft". Кроме определения четырех уже известных сегментов, модуль А опре- деляет сегмент STACK для организации блока памяти, который в последст- вии будет использоваться программным стеком, а также задает порядок редактирования указанных пяти сегментов. Указание порядка редактирова- ния дает возможность программисту определять и задавать сегменты в лю- бом произвольном порядке: это очень существенная возможность, потому что ассемблер не может обрабатывать программы со ссылками вперед. Если ассемблер MACM и редактор LINK находятся на одном и том же диске с .ASM-файлами, следующие команды можно занести в командный файл: MASM STRUCA; MASM STRUCB; MASM STRUCC; LINK STRUCA+STRUCB+STRUCC/M; Эти команды вызовут ассемблирование и редактирование всех пере- численных .ASM-файлов, в результате чего будет построена карта распре- деления памяти STRUCA.MAP, показанная на рис. 4-11. ---------------------------------------------------------------------- НАЧАЛО КОНЕЦ ДЛИНА ИМЯ КЛАСС 00000H 0000CH 0000DH _TEXT CODE 0000EH 0001FH 00012H _DATA DATA 00020H 0003DH 0001EH CONST CONST 0003EH 0004EH 00011H _BSS BSS 00050H 0014FH 00100H STACK STACK ИСХОДНАЯ ГРУППА ПРОГРАММА 0000:0 DGROUP АДРЕС ОБЩЕЕ ИМЯ 0000:000B PROC_B 0000:000C PROC_C АДРЕС ОБЩЕЕ ЗНАЧЕНИЕ 0000:000B PROC_B 0000:000C PROC_C ТОЧКА ВХОДА ПРОГРАММЫ 0000:0000 ---------------------------------------------------------------------- Рис. 4-11. Карта распределения памяти .EXE-программы Приведенная выше карта распределения памяти соответствует диаг- рамме памяти, показанной на рис. 4-12. 1.6. Использование современных моделей памяти ком- пиляторов фирмы Microsoft До сих пор мы анализировали различные аспекты создания .EXE-прог- рамм на языке ассемблера, теперь остановимся более подробно на том, как компиляторы фирмы Microsoft с языков высокого уровня порождают .EXE-программы из исходных файлов, подготовленных на языках высокого Абсолютный адрес Размер в байтах 00150H-Ў-------T--------T-------------------¬ ---- ---- ¦ ¦ ¦ ¦ • ¦ ¦ Класс ¦ STACK (A) ¦ ¦ ¦ ¦ STACK ¦ ¦ 256 ¦ ¦ ¦ ¦ ¦ ¦ 00050H-Ў¦ - - -+--------+-------------------+ ---- ¦ ¦ ¦остаток от выравнивания PARA¦ 1 ¦ 0004FH-Ў¦ - - -+--------T-------------------+ -------- ¦ ¦ ¦ ¦ BSS (C) ¦ 5 • ¦ 0004AH-Ў¦ - - -+- - - - +-------------------+ ---- ¦ ¦ ¦ ¦ ¦ост.от выравн.WORD ¦ ¦ ¦ 00049H-Ў¦ - - -+- Класс-+-------------------+ ¦ ¦ ¦ ¦ ¦ BSS (B) ¦ 5 ¦ ¦ 00044H-Ў¦группа+- BSS -+-------------------+ ---- 15 ¦ ¦ ¦ ¦ост.от выравн.WORD ¦ ¦ ¦ 00043H-Ў¦DGROUP+- - - - +-------------------+ ¦ ¦ ¦ ¦ ¦ BSS (A) ¦ 5 ° 321 0003EH-Ў¦- - - +--------+-------------------+ ---------- ¦ ¦ ¦ ¦ CONST (C) ¦ 10 • ¦ 00034H-Ў¦ - - -+-Класс -+-------------------+ ---- ¦ ¦ ¦ ¦ ¦ CONST (B) ¦ 10 ¦ ¦ 0002AH-Ў¦ - - -+-CONST -+-------------------+ ---- 30 ¦ ¦ ¦ ¦ CONST (A) ¦ 10 ° ¦ 00020H-Ў¦ - - -+--------+-------------------+ --------- ¦ ¦ ¦ ¦ DATA (C) ¦ 6 • ¦ 0001AH-Ў¦ - - -+-Класс- +-------------------+ ---- ¦ ¦ ¦ ¦ ¦ DATA (B) ¦ 6 ¦ ¦ 00014H-Ў¦ - - +-DATA -+-------------------+ ---- 18 ¦ ¦ ¦ ¦ DATA (A) ¦ 6 ° ¦ 0000EH-Ў¦ - - -+--------+-------------------+ --------- ¦ ¦ ¦остаток от выравнивания WORD¦ 1 ° 0000DH-Ў+------+--------T-------------------+ --------- ---- ¦ ¦ TEXT (C) ¦ 1 • 0000CH-Ў¦ - - Класс- - -+-------------------+ ---- ¦ ¦ ¦ TEXT (B) ¦ 1 ¦ 0000BH-Ў¦ - - CODE - - +-------------------+ ---- 13 базовый ¦ ¦ TEXT (A) ¦ 11 ° адрес -Ў00000H-ЎL---------------+-------------------- --------- DGROUP Рис. 4-12. Структура .EXE-программы, описанной в примере уровня. Даже программисты, работающие с языком ассемблера, могут заин- тересоваться предлагаемым материалом и, возможно, воспользоваться в своей работе пятью стандартными моделями памяти, описанными ниже. Дальнейшее обсуждение относится к компилятору языка Си, версия 4.0, фирмы Microsoft, который вместе с компилятором языка FORTRAN, версия 4.0, представляет собой самый современный существующий генера- тор программного кода. Эти новейшие компиляторы генерируют код на ос- нове трех-пяти следующих стандартных, выбираемых програмистом, прог- рамных структур, называемых моделями памяти. Обсуждение этих моделей памяти будет проводиться с точки зрения использования их компилятором Си фирмы Microsoft и сопровождаться комментариями, касающимися разли- чий для компилятора языка FORTRAN фирмы Microsoft. М_а_л_а_я (ключ компилятора Си - /AS). Эта модель, подразумевае- мая по умолчанию, состоит из одного сегмента программного кода и одно- го сегмента данных. Весь код должен помещаться в 64 Кбайта, а все дан- ные - в дополнительные 64 Кбайта. Большинство программ на языке Си по- падают в эту категорию. Данные могут превысить ограничение в 64 Кбайт только в том случае, если используются атрибуты большой размерности, вынуждающие компилятор использовать удаленную адресацию, а редактор - помещать данные в отдельные сегменты. Специальный ключ "порог длины данных",описанный для компактной модели памяти, игнорируется компиля- тором Си при использовании малой модели. Компилятор Си использует по умолчанию имя _TEXT для сегмента программного кода и имя _DATA - не- удаленных/небольших (по объему) данных. Программы на языке FORTRAN мо- гут генерировать подобие этой модели только путем использования ключей /NM (модуль имени) и /AM (средняя модель) компилятора в сочетании с атрибутом near для всех описаний подпрограмм. С_р_е_д_н_я_я (ключ /AM для компиляторов Си и FORTRAN). Эта мо- дель состоит из одного сегмента данных, но разбивает программный код на несколько кодовых сегментов. Все данные должны помещаться в 64 Кбайта, но ограничение 64 Кбайта на длину программного кода касается каждого модуля отдельно. Данные могут превысить ограничение в 64 Кбайт только в том случае, если используются удаленные и большие по объему атрибуты, вынуждающие компилятор использовать удаленную адресацию, а редактор - помещать удаленные и большие по объему данные в отдельные сегменты. Специальный ключ "порог длины данных", описанный для компак- тной модели, игнорируется компилятором Си при использовании средней модели. Компилятор использует по умолчанию имя сегмента _DATA для всех неудаленных/небольших по объему данных и шаблон "модуль _TEXT" для об- разования имен всех кодовых сегментов. Элемент 'модуль' шаблона 'мо- дуль _TEXT' должен быть заменен компилятором именем исходного модуля. Например, если с помощью средней модели компилируется исходный модуль HELPFUNC.C, компилятор порождает кодовый сегмент HELPFUNC_TEXT. Компи- лятор FORTRAN, версия 4.0, полностью поддерживает среднюю модель. К_о_м_п_а_к_т_н_а_я (ключ /AC компилятора Си). Эта модель состоит только из одного кодового сегмента, но разбивает данные на несколько сегментов данных. Весь программный код должен помещаться в 64 Кбайта, но данным разрешается занимать все оставшееся свободное пространство . Необязательный ключ "порог длины данных" (/Gt) компилятора Си управля- ет размещением больших по объему данных в дополнительные сегменты дан- ных, оставляя более короткие данные в используемом по умолчанию сег- менте данных, для обеспечения скорейшего к ним доступа. Отдельные поля данных в програме, работающей с компактной моделью, не могут превышать 64 Кбайта, если они явно не описаны как большие по объему. Компилятор использует по умолчанию имя сегмента _TEXT для всех кодовых сегментов и шаблон 'модуль # _DATA' для создания имен всех сегментов данных. Элемент 'модуль' должен быть заменен компилятором на имя исходного мо- дуля; элемент # заменяется на цифру, которую компилятор увеличивает для каждого дополнительного сегмента данных. Компилятор начинает с цифры 5 и далее прибавляет по единице. Например, если имя исходного модуля - HELPFUNC.C, первому сегменту данных компилятор присвоит имя HELPFUNC5_DATA. Программы на языке FORTRAN могут генерировать подобие этой модели только путем использования ключей компилятора /NM (модуль имени) и /FL (большая модель) в сочетании с атрибутом near для всех описаний подпрограм. Б_о_л_ь_ш_а_я (ключ /AL для компиляторов Си и FORTRAN). Эта мо- дель создает несколько кодовых сегментов и несколько сегментов данных. Компилятор обрабатывает данные подобно тому, как это делается для ком- пактной модели, а программный код так, как это делается в средней мо- дели. Компилятор FORTRAN, версия 4.0, полностью поддерживает большую модель. О_ч_е_н_ь б_о_л_ь_ш_а_я (ключ /AH для компиляторов Си и FORTRAN). Размещение сегментов в этой модели выполняется по тем же правилам, что и в большой модели. Различие состоит лишь в том, что некоторые поля данных могут иметь длину более 64 Кбайт. В этой модели компилятор ге- нерирует специальный код для индексирования массивов или перемещений указателей в пределах границ сегмента, успешно преобразуя при этом микропроцессорную память, адресуемую сегментами в линейно адресуемую память. Таким образом, эта модель очень удобна для переноса в другую вычислительную среду программ, написанных для процесоров с линейной адресацией. Сделует однако помнить, что потеря скорости выполнения - расплата программы за такого рода свободу адресации. Если программа и содержит какие-то структуры данных, превышающие по своей длине 64 Кбайта, скорее всего их очень мало. В этом случае лучше избегать ис- пользования очень большой модели, а просто явно объявить эти поля дан- ных большими по объему с помощью специального ключа в пределах исход- ного модуля. Это упростит все обычные поля данных, так как в них не будут использоваться средства дополнительной адресации. Компилятор FORTRAN, версия 4.0, полностью поддерживает очень большую модель. На рис. 4-13 показан пример расположения сегментов, порожденных програмой, использующей очень большую модель памяти. Пример содержит два исходных модуля: MSCA.C и MSCB.C. В каждом исходном модуле описано столько данных, что компилятор вынужден создать по два дополнительных сегмента для каждого модуля. На диаграмме не отражено все то многооб- разие сегментов, которые возникают в результате подключения динамичес- кой библиотеки и в результате компиляции, рассчитанной на последующее использование отладчика Code View. Обратите внимание, что, если программа объявляет слишком большое количество небольших полей данных, ее принятый по умолчанию сегмент данных(_DATA) может превысить 64 Кбайта, независимо от того, какая мо- дель специфицирована. Это происходит по той причине, что все поля дан- ных удовлетворяют требованию пороговой длины (ключ/Gt компилятора), и компилятор вынужден заносить их в сегмент с именем _DATA. Для решения этой проблемы рекомендуется уменьшить порог длины данных или явно за- дать удаленное описание в пределах исходного модуля. Группы Классы Сегменты -------T--------T------------¬ўSMCLH: программный стек ¦ ¦STACK ¦ STACK ¦ ¦ +--------+------------+ўSM: все неинициализируемые глобальные ¦ ¦ ¦ ¦ поля. ¦ ¦ ¦ c_common ¦ CLH: Не заполнено ¦ ¦ BSS +------------+ ¦ ¦ ¦ ¦ўSMCLH:все неинициализируемые не удален- ¦ ¦ ¦ _BSS ¦ ные/очень большие по объему поля ¦ +--------+------------+ ¦ ¦ ¦ ¦ўSMCLH:константы (константы с плавающей ¦DGROUP¦ CONST ¦ CONST ¦ точкой, адреса сегментов и т.п.) ¦ +--------+------------+ ¦ ¦ ¦ ¦ўSMCLH:все поля, нигде больше не про- ¦ ¦ DATA ¦ _DATA ¦ должающиеся +------+--------+------------+ ¦ ¦ ¦ ¦ўSM:отсутствуют, CLH:все неинициализи- ¦ ¦FAR_BSS ¦ FAR_BSS ¦ руемые глобальные поля ¦ +--------+------------+ ¦ ¦ ¦ ¦ў(только для MSCB) SM:удаленные/очень ¦ ¦ ¦MSCB6_DATA ¦ большие по объему данные; ¦ ¦ +------------+ CLH:данные длиннее заданного порога ¦ ¦ ¦ ¦ў(только для MSCB) SM:удаленные/очень ¦ ¦ ¦MSCB5_DATA ¦ большие по объему данные; ¦ ¦FAR_DATA+------------+ CLH:данные длиннее заданного порога ¦ ¦ ¦ ¦ў(только для MSCB) SM:удаленные/очень ¦ ¦ ¦MSCA6_DATA ¦ большие по объему данные; ¦ ¦ ¦ ¦ CLH:данные длиннее заданного порога ¦ ¦ +------------+ў(только для MSCB) SM:удаленные/очень ¦ ¦ ¦ ¦ большие по объему данные; ¦ ¦ ¦MSCA5_DATA ¦ CLH:данные длиннее заданного порога ¦ +--------+------------+ ¦ ¦ ¦ ¦ўSL:все программы; ¦ ¦ ¦ TEXT ¦ MLH:только программы динамической биб- ¦ ¦ +------------+ лиотеки ¦ ¦ CODE ¦MSCB_TEXT ¦ўSC:отсутствуют; MLH:код модуля MSCB.C ¦ ¦ +------------+ ¦ ¦ ¦MSCA_TEXT ¦ўSC:отсутствуют; MLH:код модуля MSCB.C L------+--------+------------- Используемые сокращения: S (Small model) - малая модель; L (Large model) - большая модель; M (Medium model) - средняя модель; H (Huge model) - очень большая модель; C (Compact model) - компактная модель. Рис. 4-13. Общая структура программы в вычислительной среде фирмы Microsoft 1.7. Внесение изменений в заголовок .EXE-файла Одновременно с большинством компиляторов фирма Microsoft постав- ляет утилиту EXEMOD (смотри руководство "Программные утилиты : EXEMOD"). Эта утилита дает возможность программисту отображать и моди- фицировать отдельные поля заголовка .EXE-файла. Ниже приводится список имен полей заголовка, которые могут изменяться с помощью утилиты EXEMOD (EXEMOD - версия 4.0). M_A_X_A_L_L_O_C. Это поле может быть модифицировано с помощью клю- ча /MAX утилиты EXEMOD. Поскольку EXEMOD работает с уже отредак- тиро- ванными файлами, ключ /MAX можно использовать для изменения поля MAXALLOC в уже готовых программах, где оно содержит присвоенное по умолчанию значение FFFFH; в этом случае программа не будет рассчиты- вать на то, что система MS-DОS распределит ей всю имеющуюся в наличии свободную память. Ключ /MAX утилиты EXEMOD работает подобно ключу /CPARMAXALLOC редактора LINK. M_I_N_A_L_L_O_C. Это поле может быть модифицировано с помощью ключа /MIN утилиты EXEMOD. В отличие от содержимого поля MAXALLOC, значение поля MINALLOC для большинства программ не является случайным. Это значение, как правило, отражает размер неинициализированной памяти и стекового пространства, которые редактор удалил из .EXE-файла; сле- довательно, программист никогда не должен уменьшать значение поля MINALLOC в .EXE-программе, подготовленной кем-то другим. Если програм- ма требует некоторого количества динамической памяти в дополнение к уже имеющимся статическим полям, значение поля MINALLOC можно увели- чить для того, чтобы быть уверенным, что эта дополнительная память бу- дет выделена программе прежде, чем она получит управление. В этом слу- чае программе уже не надо будет проверять, достаточное ли количество памяти было выделено системой MS-DOS для ее нужд. Однако, такой же ре- зультат может быть достигнут без использования утилиты EXEMOD путем объявления этого минимального дополнительного количества памяти как неинициализируемого поля в конце программы. Н_а_ч_а_л_ь_н_о_е з_н_а_ч_е_н_и_е р_е_г_и_с_т_р_а SP. Это поле может быть модифицировано с помощью ключа /STACK с целью увеличения или уменьшения размера программного стека. Однако модификация поля 'начальное значение регистра SP' может привести к аварийному заверше- нию программы, если она готовилась с помощью компиляторов фирмы Microsoft более ранних версий, чем компилятор Си (версия 3.0); компи- лятор Паскаль (версия 3.3); компилятор с языка FORTRAN (версия 3.3). Компиляторы с других языков также могут иметь подобное ограничение. Ключ /STACK можно использовать и для программ, написанных на макроас- семблере, если стековое пространство присоединяется в самом конце программы, однако в таком случае более разумным представляется изме- нить размер стекового сегмента при его объявлении. Редактор также име- ет ключ /STACK, который может использоваться с этой же целью. З_а_м_е_ч_а_н_и_е. Если задан ключ /H, утилита EXEMOD отображает на экране текущие значения полей заголовка .EXE-файла. Этот ключ не должен использоваться с другими ключами. Если не задано никаких клю- чей, утилита EXEMOD также отображает поля заголовка на экране. П_р_е_д_у_п_р_е_ж_д_е_н_и_е. Утилита EXEMOD работает правильно и с упакованными .EXE-файлами, созданными с помощью программы EXEPACK или ключа редактора /EXEPACK. Однако, в этом случае важно использовать такую версию утилиты EXEMOD, которая согласована с редактором или ути- литой EXEPACK. Возможные изменения в методах упаковки в будущем могут привести к несовместимости между утилитой EXEMOD и несвязанными с ней версиями редактора или утилиты EXEPACK. 1.8. Исправление .EXE-программы с помощью утилиты DEBUG Каждый опытный программист знает, что в любой программе есть хотя бы одна ненайденная ошибка. Если программа передается другим пользова- телям, очень возможно, что появится необходимость передать этим поль- зователям исправления, в случае обнаружения такого рода ошибки. Один из недорогих способов внесения изменений, применяемый многими крупными фирмами, состоит в рассылке пользователям инструкций размером в одну страницу, объясняющих, как нужно исправить программу, чтобы устранить ошибку. Процедура внесения исправлений в программу обычно включает заг- рузку программного файла утилитой DEBUG, поставляемой с MS-DOS, внесе- ние новых байтов в образ программы и перезапись программного файла об- ратно на диск. К сожалению, утилита DEBUG не может загрузить .EXE-программу в память и затем вернуть ее обратно на диск в .EXE-фор- мате. Программист должен заставить DEBUG исправить файлы .EXE-програм- мы с помощью процедуры, описанной ниже. Смотри руководство "Программ- ные утилиты: DEBUG". З_а_м_е_ч_а_н_и_е. Прежде чем начинать процедуру исправления, пользователь должен сделать резервную копию прораммы. 1. Переименуйте .EXE-файл, используя такое расширение имени фай- ла, которое не имеет специального значения для утилиты DEBUG. (Избе- гайте таких сочетаний, как .EXE, .COM и .HEX.) Например, последова- тельность символов MYPROG.BIN может служить хорошим временным именем для файла MYPROG.EXE, потому, что утилита DEBUG не воспринимает файл с расширением .BIN как специальный. Она загрузит весь образ файла MYPROG.BIN, включая .EXE-заголовок и таблицу перемещений, начиная со смещения 100H в пределах программного сегмента типа .COM (как указыва- лось ранее). 2. Локализуйте то место в секции загрузочного модуля образа .EXE-файла, в которое необходимо внести исправление. Приведенное выше описание образа .EXE-файла, листингов компилятора/ассемблера, карт распределения памяти редактора помогут локализовать ошибку в представ- лении .EXE-файла. Утилита DEBUG загружает образ файла, начиная со сме- щения 100Н в пределах программного сегмента типа .COM, поэтому прог- раммист должен учитывать это смещение при вычислении адресов в рамках представления файла. Кроме того, следует учитывать, что листинги ком- пилятора и карты распределения памяти редактора содержат адреса, вы- численные относительно начала образа программы в рамках .EXE-файла, а не относительно начала самого файла. Поэтому программист прежде всего должен воспользоваться информацией, содержащейся в заголовке .EXE-фай- ла, чтобы определить, где в файле начинается загрузочный модуль (образ программы). 3. Используйте команды Е (Ввести данные) и A (коды команд ассемб- лера) утилиты DEBUG для внесения исправлений. (Обычно пользовательские инструкции по внесению исправлений содержат просто адреса, по которым следует внести исправления. Как этот адрес определяется, пользователю знать не обязательно.) 4. Как только исправления внесены, выдайте команду W (Записать файл) утилиты DEBUG, чтобы правильный образ файла переписать обратно на диск с тем же самым именем файла, при условии, что поправка не из- менила длину программы. Если размер программы увеличился, измените сначала соответствующие поля длины в заголовке .EXE-файла и используй- те команду R (Отобразить или модифицировать регистры) утилиты DEBUG с тем, чтобы модифицировать содержимое регистров BX и CX так, чтобы они содержали новую длину образа файла. Потом используйте команду W для записи образа файла обратно на диск с тем же самым именем. 5. Используйте команду Q (Выход) утилиты DEBUG для возврата на командный уровень системы MS-DOS и переименуйте обратно файл, чтобы он имел правильное расширение. 1.9. Заключение по .EXE-программам Итак, можно сказать, что .EXE-программы и .EXE-файлы представляют достаточно большие возможности для проектирования и разработки прог- рамм, обеспечивая программисту необходимую свободу при создании круп- ных приложений. Программы, подготовленные с помощью компиляторов фирмы Microsoft языков высокого уровня , могут использовать пять стандартных структур программ (малая, средняя, компактная, большая и очень большая модели). Эти стандартизированные модели являются прекрасным примером того, как можно структурировать программы, разрабатываемые на языке ассемблера. 2. .COM-программа Большинство различий между .COM-программами и .EXE-программами возникает по той причине, что файлы .COM-программ не имеют заголовка. Естественно, .COM-программы не обладают теми преимуществами, которые обеспечиваются .EXE-заголовком. Отсутствие заголовка не дает возможности MS-DOS узнать, сколько памяти необходимо .COM-программе сверх размера образа программы. Поэ- тому MS-DOS вынуждена всегда выделять наибольший свободный блок памяти .COM-программе, независимо от реальных потребностей программы. Как уже указывалось при рассмотрении .EXE-программ, такое распределение блока свободной памяти, как правило, приводит к выделению всей оставшейся свободной памяти, что, как известно, может осложнить работу многоза- дачных программ супервизора. Заголовок .EXE-программы содержит также таблицу указателей пере- мещений прямых адресов сегментов. Из-за отсутствия такой таблицы .COM-программы не могут иметь адресные ссылки на метки, заданные в ди- рективах SEGMENT, за исключением адресной директивы SEGMENT AT. Если же .COM-программа использует такие ссылки, MS-DOS не сможет настроить эти адреса с учетом адреса реального сегмента, в который была загруже- на программа. Смотри ниже раздел "Подготовка .COM-программы". Структура .COM-программы разработана, главным образом, для того, чтобы обеспечить выполнение большого числа программ, подготовленных в системе CP/M и перенесенных в MS-DOS. Кроме того, структура .COM-прог- рамм часто используется для того, чтобы избежать добавления 512 или даже большего количества байтов .EXE-заголовка к маленьким простым программам, которые сами часто не превышают 512 байтов. Структура .COM-программы имеет важное преимущество: организация памяти у нее такова, что PSP находится в том же самом адресном сегмен- те, что и вся программа. Таким образом, в .COM-программах легче обра- батывать поля PSP. 2.1. Передача управления .COM-программе После того, как .COM-программе выделен наибольший блок свободной памяти, в младших байтах этого блока MS-DOS строит PSP. Не существует различий между PSP, которые MS-DOS строит для .COM-программ и PSP для .EXE-программ. Подобно тому, как и для .EXE-программ, MS-DOS определя- ет начальные значения регистров AL и AH, а затем загружает весь образ .COM-файла в память непосредственно за PSP. Так как .COM-файлы не име- ют заголовка, из которого можно узнать размер файла, MS-DOS пользуется информацией, имеющейся в оглавлении диска, чтобы определить длину об- раза программы. Система загружает программу в таком виде, в каком она находится в файле, не проверяя содержимое файла. Затем MS-DOS устанавливает сегментные регистры DS, ES, CS, SS на начало PSP. Если представляется возможным выделить хотя бы 64 Кбайт программе, MS-DOS устанавливает регистр SP на позицию FFFH+1(0000H) для построения стека; если программе доступны менее 64 Кбайт, MS-DOS устанавливает первый байт регистра SP на наибольшее смещение в преде- лах программы. В противном случае MS-DOS выделяет для программного стека единственное слово со смещением 0000Н, которое использует при завершении программы. И наконец, MS-DOS передает управление программе, установив ре- гистр CS на адрес сегмента, содержащего PSP, а регистру IP присвоив значение 0100Н. Это означает, что точка входа программы должна нахо- диться в самом начале образа программы, что будет ясно и из последую- щих примеров. На рис.4-14 показана общая структура .COM-программы в момент по- лучения управления от системы MS-DOS. ------------¬ Образ .COM-программы в памяти ¦ ° ¦ ------T-----T---------------------¬ ---- ¦ ¦ ¦ ¦ ¦ • SP=FFFEH---- ¦ 00H ¦ 00H ¦ ¦ ¦ +-----+------ ¦ ¦ ¦ Оставшаяся свободная ¦ ¦ ¦ память в пределах ¦ ¦ ¦ первых 64 Кбайт, ¦ ¦ ¦ выделенных программе ¦ ¦ ¦ (при условии, что все ¦ ¦ ¦ 64 Кбайт были доступны) ¦ 64 Кб ¦---------------------------------¦ ¦ ¦ ¦ ¦ ------------------¬ --Ў¦ Образ .COM-программы из файла ¦ ¦ ¦ ¦ ¦ ¦---------------------------------¦ўIP= ¦ ¦ Oбраз +--- ¦ ¦ =0100H ¦ ¦ .COM-программы ¦ ¦ PSP ¦ ° L------------------ L----------------------------------ўCS,DS,--- EC,SS Рис. 4-14. .COM-программа: диаграмма распределения памя- ти с указателями регистров 2.2. Завершение .COM-программы .COM-программа может использовать все методы завершения, описан- ные для .EXE-программ, но самым предпочтительным для нее является ис- пользование процесса завершения с помощью системного прерывания 21Н с функцией кода завершения (4СН). Если .COM-программа должна быть сов- местима с версиями MS-DOS более ранними, чем 2.0, она может использо- вать любой из более старых методов завершения, включая даже те, ис- пользование которых было затруднительно в .EXE-программах, так как в процессе работы .COM-программ регистр CS указывает на PSP, что и явля- ется особенным требованием этих методов. 2.3. Подготовка .CОM-программы .COM-программа готовится аналогично .EXE-программе, а затем кон- вертируется с помощью утилиты MS-DOS EXE2BIN. Смотри руководство "Программные утилиты: EXE2BIN". Однако имеются некоторые ограничения для .COM-программ. Во-пер- вых, .COM-программа не может быть длиннее, чем 64 Кбайта минус 100Н байтов для PSP и минус 2 байта для нулевого слова, первоначально выде- ляемого для стека. Кроме того, только один сегмент или, по крайней мере, одна адрес- ная группа находится внутри программы. Следующие два примера показыва- ют, как можно структурировать .COM-программу, чтобы выполнить оба ука- занных требования, а также требования MASM, чтобы поля данных предшес- твовали программному коду в исходном файле. В программе COMPRG1.ASM (рис. 4-15) объявляется лишь один сегмент (COMSEG), следовательно никаких специальных действий не требуется при использовании оператора макроассемблера OFFSET. Смотри выше раздел "Директива макроассемблера GROUP". В программе COMPROG2.ASM ( рис.4-16 ) отдельно объявляются кодовый сегмент (CSEG) и сегмент данных (DSEG), а директива GROUP объединяет их в один общий адресный блок. Таким об- разом, программист может объявить поля данных в начале исходного фай- ла, а редактор поместит сегмент данных (DSEG) после кодового сегмента (CSEG) в ходе редактирования программы, как это выполнялось для .EXE-программ. Этот второй пример моделирует процесс структурирования программы, выполняемый в системе CP/M макроассемблером фирмы Microsoft Macro-80(M80) и редактором Link-80 (L80). Пример может быть легко рас- ширен с тем, чтобы включить сегмент COMMON и другие дополнительные сегменты. ---------------------------------------------------------------------- COMSEG SEGMENT BYTE PUBLIC 'CODE ASSUME CS:COMSEG,DS:COMSEG,ES:COMSEG,SS:COMSEG ORG 0100H BEGIN: JMP START ;Разместите здесь поля данных START: ;Разместите здесь текст программы MOV AX,4C00H INT 21H COMSEG ENDS END BEGIN Рис. 4-15. .COM-программа, у которой данные размещаются в начале ---------------------------------------------------------------------- ---------------------------------------------------------------------- CSEG SEGMENT BYTE PUBLIC 'CODE' CSEG ENDS DSEG SEGMENT BYTE PUBLIC 'DATA' DSEG ENDS COMGRP GROUP CSEG,DSEG DSEG SEGMENT ;Разместите здесь данные DSEG ENDS CSEG SEGMENT ASSUME CS:COMGRP,DS;COMGRP,ES:COMGRP,SS:COMGRP ORG 0100H BEGIN: ;Разместите здесь программы. Не забывайте использовать ;OFFSET COMGRP:LABEL всюду, где используете OFFSET. MOV AX,4C00H INT 21H CSEG ENDS END BEGIN Рис. 4-16. .COM-программа, у которой данные находятся в конце ---------------------------------------------------------------------- Эти примеры демонстрируют и другие важные особенности подготовки .COM-программ. Например, оператор ORG 0100H в обоих примерах указывает макроасемблеру начать ассемблирование кода со смещения 100Н внутри сегмента. Это означает для системы MS-DOS передачу управления програм- ме в точку входа IP=0100H. Кроме того, метка точки входа (BEGIN) сле- дует сразу за оператором ORG и появляется снова как параметр оператора END. Оба эти факта подтверждают то, что .COM-программы имеют точку входа со смещением 100Н. Если какое-нибудь из указанных действий не выполнено, утилита EXE2BIN системы MS-DOS не сможет правильно преобра- зовать построенный редактором .EXE-файл в .COM-файл. В частности, если .COM-программа объявляет точку входа (определяемую параметром операто- ра END) со смещением, не равным 0100Н и не равным 0000Н, утилита EXE2BIN отвергает .EXE-файл, когда программист пытается его конверти- ровать. Если в программе не объявлено ни одной точки входа или объяв- лена точка входа со смещением 0000Н, утилита EXE2BIN считает, что .EXE-файл должен конвертироваться в двоичное представление, а не в .COM-файл. Когда утилита EXE2BIN преобразует .EXE-файл в бинарный файл, а не в .COM-файл, она не удаляет дополнительные 100Н байтов, ко- торые редактор помещает перед программным кодом в результате выполения команды ORG 0100H. Таким образом, на самом деле, когда MS-DOS загружа- ет программу в память, она начинается со смещения 200Н, но все адрес- ные ссылки в процессе ассемблирования и редактирования получают базо- вый адрес со смещение 100Н. В результате программа, а, возможно, и вся система в целом могут аварийно завершиться. Следует также отметить, что .COM-программа не должна иметь прямых сегментных адресных ссылок на любые сегменты, составляющие программу. То есть .COM-программа не может ссылаться на метки сегментов или любые другие метки с помощью указателей на удаленные адреса (FAR). (Это пра- вило не запрещает программе ссылаться на метки сегментов с помощью ад- ресной директивы SEGMENT AT.) Ниже приводятся примеры прямых сегмент- ных адресных ссылок, которые н_е д_о_л_ж_н_ы использоваться в .COM-программах. PROC_F PROC FAR PROC_A ENDP CALL PROC_A ;межсегментный вызов JMP PROC_A ;межсегментный переход или EXTRN PROC_A:FAR CALL PROC_A ;межсегментный вызов JMP PROC_A ;межсегментный переход или MOV AX,SEG,SEG_A ;адрес сегмента DD LABEL_A ;указатель на сегмент: смещение И, наконец, не разрешается использовать в .COM-программе сегменты объединительного типа STSCK. Если в программе объявляется сегмент объ- единительного типа STACK, редактор занесет начальные значения регист- ров SS и SP в заголовок .EXE-файла и тем самым заставит утилиту EXE2BIN отвергнуть .EXE-файл. В .COM-программе не могут явно описы- ваться стеки, однако может резервироваться память в обычных сегментах, для которой потом может инициализироваться регистр SP уже п_о_с_л_е того, как программа получит управление. Об отсутствии стекового сег- мента редактор уведомляет предупреждающим сообщением. Если после ассемблирования и редактирования строится .EXE-файл, его необходимо отконвертировать в бинарный файл с расширением .COM с помощью утилиты EXE2BIN так, как это делается в следующем примере для файла YOURPROG.EXE: C>EXE2BIN YOURPROG YOURPROG.COM <ввод> Нет необходимости удалять или переименовывать .EXE-файл с тем же самым именем, что и .COM-файл, прежде чем выполнять .COM-файл, из-за того, что оба они находятся в одной и той же библиотеке, так как в MS-DOS принят следующий порядок выполнения программ с одинаковыми име- нами: сначала выполняются .COM-файлы, затем - .EXE-файлы, и, наконец, .BAT-файлы. Однако более безопасно сразу же удалить .EXE-файл после преобразования его в .COM-файл, если .COM-файл может быть переименован или переписан в другую библиотеку. Если же .EXE-файл, созданный для преобразования в .COM-файл, случайно начнет выполняться, он может раз- рушить операционную систему. 2.4. Исправление .COM-программы с помощью утилиты DEBUG Как уже указывалось для .EXE-файлов, фирма-разработчик программ- ного обеспечения может направлять своим пользователям инструкции о том, как исправить обнаруженную ошибку. Описанный подход к корректи- ровке програмного обеспечения даже в большей степени подходит для .COM-файлов, чем для .EXE-файлов. Например, так как .COM-файлы содержат только программный код, их не надо переименовывать для того, чтобы потом читать и писать с по- мощью утилиты DEBUG. Пользователю необходимо лишь объяснять, как заг- рузить .COM-файл в DEBUG, как исправить программу и как записать пра- вильный вариант обратно на диск. Процедура вычисления адресов и исп- равляемых значений оказывается проще, так как в .COM-файле отсутствует заголовок и связанные с ним сложности. Если учесть все указанные фак- ты, методика исправления .COM-программы остается аналогичной описанной для .EXE-программ. 2.5. Заключение по .COM-программам В заключение следует отметить, что подготовка программ с помощью .COM-структур проще и имеет много ограничений по сравнению с .EXE-структурами, так как программисту для выбора предоставляется лишь одна единственная модель памяти. Кроме того, файлы .COM-программ не содержат 512-байтного (или даже более) заголовка, характерного для .EXE-файлов; следовательно, структура .COM-программ хорошо подходит для маленьких программ, добавление 512 байтов заголовка к которым, возможно, удвоит длину файла. 3. Перечень различий В следующей таблице сведены отличительные характеристики .COM- и .EXE-программ. --------------------------------------------------------------------- .COM-программа .EXE-программа --------------------------------------------------------------------- Максимальная 65536 байтов минус 256 Нет ограничений длина байтов для PSP минус 2 байта для стека Точка входа PSP:0100H Определяется оператором END Регистр CS Адрес PSP Адрес сегмента, содер- при входе в жащего точку входа программу программы Регистр IP 0100Н Смещение точки входа при входе в внутри сегмента программу Регистр DS Адрес PSP Адрес PSP при входе в программу Регистр ES Адрес PSP Адрес PSP при входе в программу Регистр SS Адрес PSP Адрес сегмента с при входе в атрибутом STACK программу Регистр SP FFFEH или адрес самого Адрес конца сегмента, при входе в верхнего слова доступной имеющего атрибут STACK программу свободной памяти, каким бы низким он не было Стек при Нулевое слово Инициализирован или входе в неинициализирован в программу зависимости от исход- ной программы Длина стека 65536 байтов минус Определяется в сегменте 256 байтов для PSP с атрибутом STACK и минус длина выполняе- мого программного кода и данных Вызовы Ближние Ближние и удаленные подпрограмм Способ Предпочтительно с Предпочтительно с завершения помощью прерывания помощью прерывания 21Н с функцией 4СН; с 21Н с функцией 4СН; помощью NEAR RET, для неявный переход на версий MS-DOS 1.X PSP:0000H для версий MS-DOS 1.X Длина файла Равна длине программы Длина программы плюс длина заголовка (по крайней мере 512 до- полнительных байтов) --------------------------------------------------------------------- Какой из форматов программист предпочитает использовать для при- ложения, обычно зависит от предполагаемой длины программы, но решение может также определяться исходя из потребностей программы адресоваться к различным сегментам памяти. Обычно небольшие программы-утилиты (типа CHKDSK и FORMAT) проектируются как .COM-программы, а большие программы (например, компилятор Си фирмы Microsoft) разрабатываются как .EXE-программы. Окончательное решение, конечно, за программистом. Кейс Берджоун Глава 5. Символьные устройства ввода/вывода Все компьютерные системы включают центральный процессор, память и периферийные устройства, используемые процессором для хранения данных и взаимодействия с внешней средой. В среде MS-DOS в число наиболее часто используемых периферийных устройств входят клавиатура (для вво- да), дисплей (для вывода) и один или несколько дисководов (для посто- янного хранения данных). Дополнительные устройства, такие как принте- ры, модемы, устройства координатного типа расширяют функциональные возможности компьютера или ориентируют на его специфическое примене- ние. В MS-DOS различают два типа устройств: блочного ввода-вывода (обычно дисководы для жестких и гибких дисков) и символьного (клавиа- тура, дисплей, принтер, коммуникационные порты). Различие между устройствами блочного и символьного ввода/вывода не всегда четко выражено. Но в общем случае, устройства блочного типа обмениваются информацией порциями, или блоками, а символьного - посим- вольно (обычно побайтно). В MS-DOS каждому устройству блочного типа в момент загрузки драйвера устройства - программы, управляющей его рабо- той - приписывается однобуквенный идентификатор . Символьное же уст- ройство идентифицируется с помощью логического имени (подобного имени файла и подчиняющегося тем же ограничениям на его формирование), вст- роенного в драйвер устройства. (См.раздел "Программирование в среде MS-DOS: Установка MS-DOS. Установка драйверов устройств.") 1. Предыстория вопроса В версиях 1.х MS-DOS, разработанных в 1981 г. для IBM PC, поддер- жка периферийных устройств осуществлялась с помощью фиксированного множества драйверов устройств, загружаемых во время инициализации сис- темы из файла IO.SYS (IMBIO.COM PC-DOS). В этих версиях MS-DOS прик- ладные программы в большой степени были независимы от устройств вво- да-вывода зв счет того, что символьные устройства рассматривались как файлы. Но в этих версиях довольно сложно было расширять состав встро- енных драйверов при подключении новых периферийных устройств. В версиях 2.0 MS-DOS была существенно упрощена адаптация системы к новым техническим средствам. В версиях 2.0 и последующих поддержива- ется установка драйверов, которые могут располагаться в различных фай- лах и присоединяться к системе путем указания в файле CONFIG.PAR соот- ветствующих директив DEVICE. (См. раздел "Команды пользователя: CONFIG.SYS: Устройства.") Хороший интерфейс между драйверами и ядром MS-DOS позволял писать драйверы для большей части периферийных устрой- ств, не изменяя при этом самой операционной системы. В CONFIG.SYS может содержаться большое количество команд DEVICE для загрузки отдельных драйверов для клавишных устройств, магнитных лент, сетевого интерфейса и тому подобное. В свою очередь, каждый драйвер разрабатывается с учетом специфических особенностей конкретно- го устройства. В момент запуска системы или рестарта устанавливаемые драйверы добавляются в список принятых по умолчанию драйверов, загружаемых из IO.SYS в момент инициализации MS-DOS. Таким образом, исчезает необхо- димость составления обширного списка включаемых по умолчанию драйве- ров, что существенно экономит память. Одним важным отличием устройств блочного и символьного типа явля- ется то, что MS-DOS при установке нового драйвера добавляет его в ко- нец списка устройств - для блочных, и в начало списка - для символьных устройств. Таким образом, в связи с тем, что в MS-DOS список устройств всегда опрашивается с головы, любой драйвер символьного устройства можно при необходимости легко заменить другим, указав идентичное логи- ческое имя устройства. В этой главе рассматриваются некоторые детали работы с символьны- ми устройствами: вывод текста на дисплей, ввод с клавиатуры, другие основные функции ввода-вывода; особенности использования функции IOCTL (прерывание 21H, функция 44H) для непосредственного взаимодействия с драйвером устройства. Большая часть представленной информации распрос- траняется только на версию 2.0 MS-DOS и последующие. 2. Доступ к символьным устройствам В среде MS-DOS есть два способа обращения к символьным устройст- вам, обеспечивающих свойство независимости программ от особенностей этих устройств. Во-первых, можно использовать реализованный в MS-DOS, начиная с версии 2.0, класс функций, взаимодействующих с устройствами и файлами с помощью системного идентификатора дескриптора файла или устройства (handle). Во-вторых, можно пользоваться традиционными функ- циями обращения к символьным устройствам, реализованным в версиях 1.х MS-DOS, которые сохранены в версии 2.0 и последующих для совместимос- ти. В связи с тем, что функции первого типа предоставляют большие воз- можности и гибкость, обсудим их в первую очередь. Системный идентификатор дескриптора - это 16-битный номер, возв- ращаемый операционной системой в момент открытия файла или устройства или создаваемый при передаче имени функциям 3CH прерывания 21H - соз- дать файл с системным идентификатором (interrupt 21H Function 3CH - Create File with Handle), 3DH - открыть файл с системным идентификато- ром (Open File with Handle), 5AH - создать временный файл (Create Temporary File), 5BH - создать новый файл (Create New File). После создания системного идентификатора его можно использовать в следующих функциях прерывания 21H: 3FH - читать файл или устройство (Read File or Device), 40H - писать в файл или устройство (Write File or Device) для обмена данными между оперативной памятью и файлом или устройством. В процессе выполнения функций открытия или создания файла по спе- цифицируемому имени, поиску в справочнике диска предшествует последо- вательный опрос цепи символьных устройств (расширение имени при этом игнорируется). Таким образом, нельзя создать файл, имя которого совпа- дает с именем одного из устройств, указанных в цепи драйверов (напри- мер, нельзя создать файл NUL.TXT); то же касается доступа к файлу. Второй способ взаимодействия с символьными устройствами состоит в использовании традиционных функций MS-DOS для этих устройств: прерыва- ние 21H, функции в диапазоне 01H до 0CH. Эти функции разработаны для прямого взаимодействия с клавиатурой, дисплеем, принтером и последова- тельным портом. Для каждого устройства есть свой набор функций, поэто- му в этом случае нет надобности в исползовании имен и системных иден- тификаторов. Но в MS-DOS, начиная с версии 2.0, эти функции интерпре- тируются с помощью функций, использующих системный идентификатор. Та- ким образом, и к этому классу функций применимы понятия "переадреса- ции" (redirection) и "сквозного канала" (pipe line). Использование функций обоих классов позволяет создавать достаточ- но мобильные программы, которые выполняются на любом компьютере, на котором установлена MS-DOS. Третий способ взаимодействия с устройства- ми, ухудшающий показатели мобильности, состоит в использовании специ- фических программ управления устройствами (размещающимися в ПЗУ - Read Only Memory(ROM)) для конкретных компьютеров (к таким функциям отно- сятся функции драйвера системы ввода-вывода - IBM PC ROM BIOS driver functions). И, наконец, четвертый путь, не позволяющий создавать мо- бильные программы, состоит в прямом обращении к адаптеру устройства, минуя операционную систему. Хотя последний способ не может быть реко- мендован для большей части приложений, его иногда приходится применять для достижения большей производительности. 3. Основные символьные устройства в MS-DOS В любой версии MS-DOS поддерживается по крайней мере следующее множество логических символьных устройств: -------------T-----------------------------------------------¬ ¦ Устройство ¦ Назначение ¦ +------------+-----------------------------------------------+ ¦ CON ¦ Клавиатура и дисплей ¦ ¦ PRN ¦ Системное устройство вывода, обычно парал- ¦ ¦ ¦ лельный порт ¦ ¦ AUX ¦ Дополнительное устройство, обычно последо- ¦ ¦ ¦ вательный порт ¦ ¦ CLOCK$ ¦ Системный таймер ¦ ¦ NUL ¦ Устройство для сборки "мусора"(bit-bucket) ¦ L------------+------------------------------------------------ Эти устройства можно открыть по имени, или для обращения к ним можно пользоваться "традиционными" функциями. Обмен происходит сим- вольными строками в любой версии MS-DOS. Данные, посылаемые на устрой- ство NUL, пропадают, а в случае чтения для этого устройства возвраща- ется признак конца файла. В PC-DOS и других совместимых с MS-DOS операционных системах обычно используются следующие логические имена устройств: -------------T-----------------------------------------------¬ ¦ Устройство ¦ Назначение ¦ +------------+-----------------------------------------------+ ¦ COM1 ¦ Первый последовательный коммуникационный ¦ ¦ ¦ порт ¦ ¦ COM2 ¦ Второй последовательный коммуникационный ¦ ¦ ¦ порт ¦ ¦ LPT1 ¦ Первый параллельный порт печати ¦ ¦ LPT2 ¦ Второй параллельный порт печати ¦ ¦ LPT3 ¦ Третий параллельный порт печати ¦ L------------+------------------------------------------------ В таких системах PRN является алиасом (синонимом) LPT1, а AUX - COM1. Можно использовать команду MODE для переадресации устройств LPT на другие устройства. (См. "Команды пользователя: MODE".) Как упоминалось выше, любой из драйверов этих устройств может быть замещен драйвером, устанавливаемым пользователем. Например, в случае, когда этот драйвер расширяет возможности или изменяет характе- ристики устройства. Одним из часто используемых альтернативных драйве- ров является ANSI.SYS, замещающий стандартный драйвер MS-DOS для уст- ройства CON, допускающий использование Esc-последовательностей ANSI для выполнения таких действий, как очистка экрана, управление позицией курсора, выбор атрибутов символов. (См. "Команды пользователя: ANSI.SYS".) 3.1. Стандартные устройства В среде версии 2.0 MS-DOS и последующих в момент выполнения любой программе доступны системные идентификаторы предварительно открытых пяти символьных устройств (называемых стандартными устройствами). Эти системные идентификаторы могут быть использованы для взаимодействия с устройствами без каких-либо предварительных действий. Перечислим эти стандартные устройства. ----------------------------------T-------------T------------¬ ¦ Имя стандартного устройства ¦ Системный ¦ Назначение ¦ ¦ ¦идентификатор¦по умолчанию¦ +---------------------------------+-------------+------------+ ¦Стандартный ввод (stdin) ¦ 0 ¦ CON ¦ ¦Стандартный вывод (stdout) ¦ 1 ¦ CON ¦ ¦Стандартное устройство ошибок ¦ 2 ¦ CON ¦ ¦ (stderr) ¦ ¦ ¦ ¦Стандартное дополнительное уст- ¦ 3 ¦ AUX ¦ ¦ ройство (stdaux) ¦ ¦ ¦ ¦Стандартная печать (stdprn) ¦ 4 ¦ PRN ¦ L---------------------------------+-------------+------------- Системные идентификаторы стандартного ввода и вывода особенно важны, потому что их можно переадресовывать. Хотя эти идентификаторы по умолчанию связываются с устройством CON (ввод с клавиатуры, вывод на дисплей), их можно ассоциировать и с другими символьными устройст- вами или файлами, используя параметры переадресации в командной строке обращения к программе: ----------------T-----------------------------------------------¬ ¦ Переадресация ¦ Результат ¦ +---------------+-----------------------------------------------+ ¦ < file ¦ Чтение со стандартного устройства ввода бу-¦ ¦ ¦ дет выполняться для файла с именем file ¦ ¦ > file ¦ Запись на стандартное устройство вывода бу-¦ ¦ ¦ дет выполняться для файла с именем file ¦ ¦ >> file ¦ Вывод на стандартное устройство вывода бу- ¦ ¦ ¦ дет осуществляться в конец файла с именем ¦ ¦ ¦ file ¦ ¦ p1:p2 ¦ Стандартный вывод программы p1 становится ¦ ¦ ¦ стандартным вводом для программы p2 ¦ L---------------+------------------------------------------------ Возможность переадресации увеличивает гибкость и возможности сис- темы. Например, программа, ориентированная на ввод с клавиатуры, может получать символы из файлов, вывод программы может назначаться в файл или на печать, можно создавать программы общего назначения для обра- ботки текстовых потоков (так называемые фильтры - filters), независи- мые от вида и размещения источников текстов. (См. "Программирование в среде MS-DOS: Разработка фильтров в среде MS-DOS".) Обычно прикладной программе не известно, производилась ли переад- ресация стандартных устройств ввода-вывода, хотя операция вывода на стандартное устройство может непредсказуемо окончиться аварийно, если переадресация была сделана на дисковый файл, а диск оказался полон. Факт переадресации можно в прикладной программе установить, используя средства IOCTL (прерывание 21H, функция 44H - Interrupt 21H Function 44H), но выяснить расположение переадресованного устройства нельзя, кроме того, невозможно выяснить, файл это или символьное устройство. 3.2. Сквозной и фильтрующий режимы обработки В MS-DOS с каждым системным идентификатором символьного устройст- ва ассоциируется режим обработки запросов на ввод-вывод. Если установ- лен сквозной режим, обмен символами между драйвером устройства и прог- раммой осуществляется без фильтрации и буферизации. Если же установлен фильтрующий режим, данные буферизуются, кроме того предпринимаются специальные действия в случае появления некоторых специальных симво- лов. В фильтрующем режиме ввода MS-DOS осуществляет посимвольное полу- чение данных с устройства, проверяя символы на Ctrl-C. Данные помеща- ются во внутренний буфер MS-DOS. Операция ввода заканчивается, если в потоке встречаются символы "возврат каретки" (carriage return) - 0DH, признак конца файла - 1AH, или получено заказанное прикладной програм- мой количество символов. Если источником данных является стандартное устройство ввода, то одиночные знаки "конец строки" (line feed) преоб- разуются в пару "возврат каретки"/"конец строки". Затем строка знаков из внутреннего буфера копируется в буфер программы с передачей ей уп- равления. В режиме фильтрующего вывода данные из буфера программы посим- вольно передаются драйверу устройства, проверяя после каждого символа ввод Ctrl-C с клавиатуры. Если вывод осуществляется на стандартное ус- тройство, которое не переадресовано, то знаки табуляции заменяются на пробелы (по восемь на знак табуляции). Вывод завершается, если переда- но указанное программой количество знаков или в потоке встречен приз- нак конца файла - 1AH. В режиме сквозного обмена данные прямо пересылаются между устрой- ством и буфером прикладной программы. Специальные знаки "возврат ка- ретки" и "конец файла" игнорируются, а ввод-вывод завершается при дос- тижении точного количества символов, указанного в запросе программы. В этом режиме не происходит посимвольного обращения к драйверу устройст- ва, клавиатура во время операции ввода-вывода не опрашивается на ввод Ctrl-C. И, наконец, символы, прочитанные с устройства стандартного ввода, не отображаются на устройство стандартного вывода. Как следует из сказанного, ввод-вывод данных в режиме сквозного обмена происходит значительно быстрее, потому что MS-DOS не осуществ- ляет обработку каждого символа. Этот режим также позволяет читать дан- ные прямо из буфера клавиатуры без перехвата символов системой (напри- мер, Control-C, Control-P, Control-S). (Если включено состояние BREAK , MS-DOS продолжает проверять значение Control-C во время выполнения других операций, таких как дисковые, и в случае обнаружения Control-C передает управление программе обработки исключительной ситуации "Control-C". ) Возможно установление режима обмена для системного идентификатора символьного устройства во время выполнения программы с помощью средств управления вводом/выводом (IOCTL) в MS-DOS (получить данные - GET DATA, установить имя устройства с данными - SET DEVICE DATA) (прерывание 21H, функция 44H, подфункции 00H и 01H). Обычно, режим обмена определяется атрибутом конкретного системно- го идентификатора, возвращаемого системой во время операции открытия файла, и влияет только на операцию ввода-вывода в программе, владеющей системным идентификатором. Однако, если с помощью средств IOCTL выби- рается режим обмена для системного идентификатора стандартного устрой- ства, это имеет глобальный эффект на поведение всей системы, потому что системные идентификаторы стандартных устройств никогда не закрыва- ются. Это может привести к непрогнозируемому поведению некоторых функ- ций ввода с клавиатуры. Следовательно, перед изменением атрибута режи- ма ввода системного идентификатора стандартного файла необходимо сох- ранить значение атрибута, а при выходе из программы - восстановить его, чтобы не нарушить работу COMMAND.COM и других приложений. В таких программах необходимо также обеспечить обработку критических ошибок и прерываний по Ctrl-C, чтобы программа не смогла завершиться непредус- мотренным образом. (См.: "Программирование в среде MS-DOS: Настройка MS-DOS: Обработка исключительных ситуаций.") 3.3. Клавиатура В рамках прерывания 21H есть два способа взаимодействия с клавиа- турой: традиционный, предполагающий использование функций 01H, 06H, 07H, 08H, 0AH, 0BH, 0CH (табл.5-1) и метод, основанный на использова- нии системного идентификатора файла с помощью функции 3FH. Оба метода имеют свои преимущества и недостатки. (См.: "Системные обращения".) Табл. 5-1. Традиционные функции символьного ввода в MS-DOS --------T---------------------T------------------T-----T----------¬ ¦Функция¦ Наименование ¦Возможность ввода ¦Эхо- ¦Реакция на¦ ¦ ¦ ¦последовательности¦отоб-¦ Ctrl-C ¦ ¦ ¦ ¦символов (за одно ¦раже-¦ ¦ ¦ ¦ ¦обращение) ¦ние ¦ ¦ +-------+---------------------+------------------+-----+----------+ ¦ 01H ¦ Символьный ввод с ¦ нет ¦ да ¦ да ¦ ¦ ¦эхо-отображением ¦ ¦ ¦ ¦ ¦ 06H ¦ Прямой ввод/вывод с ¦ нет ¦ нет ¦ нет ¦ ¦ ¦консоли ¦ ¦ ¦ ¦ ¦ 07H ¦ Сквозной символьный ¦ нет ¦ нет ¦ нет ¦ ¦ ¦ввод без эхо-отобра- ¦ ¦ ¦ ¦ ¦ ¦жения ¦ ¦ ¦ ¦ ¦ 08H ¦ Символьный ввод без ¦ нет ¦ нет ¦ да ¦ ¦ ¦эхо-отображения ¦ ¦ ¦ ¦ ¦ 0AH ¦ Буферизованный ввод ¦ да ¦ да ¦ да ¦ ¦ ¦с клавиатуры ¦ ¦ ¦ ¦ ¦ 0BH ¦ Проверить состояние ¦ нет ¦ нет ¦ да ¦ ¦ ¦клавиатуры ¦ ¦ ¦ ¦ ¦ 0CH ¦ Очистка буфера, чте-¦ * ¦ * ¦ * ¦ ¦ ¦ние с клавиатуры ¦ ¦ ¦ ¦ L-------+---------------------+------------------+-----+----------- Примечание. Значение * зависит от перечисленных функций, указан- ных в регистре AL. Первые четыре функции в действительности очень похожи. Все они возвращают символ в регистре AL. Они отличаются только эхо-отображени- ем и реакцией на Ctrl-C. Обе функции 06H и 0BH могут быть использованы для проверки состояния клавиатуры (то есть была ли нажата клавиша и происходит ли ожидание чтения символа программы); функцией 0BH пользо- ваться проще, зато функция 06H не реагирует на ввод Ctrl-C. Функция 0AH используется для чтения последовательности символов ("буферизованная строка"), что означает накопление в буфере целой строки до передачи ее программе. Признаком завершения строки является нажатие клавиши Enter (Ввод) или достижение указанного программой мак- симального количества символов (до 255). Во время ввода строки можно использовать обычные ключи редактирования (левые и правые стрелки и функциональные клавиши); в программу передается только окончательно отредактированная строка. Функция 0CH позволяет предварительно очистить буфер ввода (в ко- тором могли оказаться данные в результате нажатия клавиш за время, прошедшее от момента обработки предыдущего ввода) перед принятием со- общения. Это средство важно, когда необходимо вывести на экран подс- казку (например, в критических ситуациях), реакцию на которую пользо- ватель предусмотреть не мог. Эту же функцию следует использовать, если необходимо получить ответ в критической ситуации (например, подтвер- дить разрешение уничтожения файла), чтобы предотвратить случайный от- вет как следствие ранее введенного символа. Функция 0CH используется совместно с одной из перечисленных выше функций, номер которой указы- вается в регистре AL. После очистки буфера управление передается функ- ции, указанной в регистре AL. Поэтому остальные параметры функции 0CH определяются используемой в ней функцией. Первым недостатком традиционных функций является плохая поддержка переадресации ввода/вывода. Если стандартный ввод переадресован на файл, то с помощью традиционных функций невозможно определить конец файла - функция ввода будет находиться в бесконечном ожидании, и сис- тема зависнет. Для использования функций ввода с клавиатуры, основанных на сис- темном идентификаторе, необходимо воспользоваться средствами MS-DOS Read File (читать файл) или Read Device (читать устройство) - прерыва- ние 21H, функция 3FH. Обычно можно воспользоваться всегда определенным системным идентификатором стандартного ввода (0), который не надо отк- рывать и который подволяет переадресовать ввод на другой файл или уст- ройство. Если необходимо обойти переадресацию и обеспечить работу с клавиатурой, можно открыть устройство CON (прерывание 21H, функция 3DH) и использовать полученный системный идентификатор вместо стандар- тного. Использование основанного на системных идентификаторах ввода поз- воляет управлять эхо-отображением, восприимчивостью к Control-C путем выбора сквозного или фильтрованного режима обмена данными с помощью средств IOCTL Get Data и Set Device Data (по умолчанию - фильтрованный ввод). Для тестирования состояния клавиатуры можно использовать функ- цию IOCTL "Проверить состояние ввода" (Check Input Status - прерывание 21H, функция 44H, подфункция 06H) или традиционную функцию проверки (прерывание 21H, функция 0BH). Основное преимущество основанного на системных идентификаторах метода для ввода с клавиатуры - это его полная аналогия файловым опе- рациям и обеспечение элегантной переадресации ввода. Этот метод позво- ляет также обрабатывать строки длиной до 65 535 символов (традиционный способ - только до 255 символов). Сказанное важно для программ, ис- пользуемых часто с переадресованным вводом и выводом (таких как филь- тры), потому что обмен с файлами более крупными блоками увеличивает эффективность операций. Единственным недостатком этого метода является использование MS-DOS, начиная с версии 2.0 (хотя теперь это, в общем, и не является ограничением). 3.3.1. Роль BIOS ПЗУ в операциях чтения с клавиа- туры Нажатие клавиши на клавиатуре в ПЭВМ IBM PC или совместимых с ни- ми генерирует прерывание аппаратуры (09H), обрабатываемое программой, входящей в состав BIOS ПЗУ. Эта программа опрашивает порты контроллера клавиатуры и переводит скан-код клавиши в ASCII-код. Результат перево- да зависит от состояния переключателей NumLock и CapsLock, а также от того, были ли нажаты клавиши Shift, Control или Alt. ( BIOS ПЗУ анали- зирует байт флагов клавиатуры, расположенный по адресу 0000:0417H, со- держащий всю текущую информацию о состоянии перечисленных специальных клавиш.) После трансляции скан-код и ASCII-код помещаются в 32-байтный (16 символов) буфер ввода клавиатуры BIOS ПЗУ. В случае использования до- полнительных клавиш, таких как функциональные клавиши или стрелки, байт ASCII-кода содержит ноль, а вся информация содержится в байте скан-кода. Буфер клавиатуры организован по кольцевому методу и управ- ляется как очередь (первый вошел - первый вышел). Для определения то- го, что буфер пуст, задействована одна позиция в буфере; поэтому в бу- фере можно поместить максимум 15 символов. Если буфер переполняется, то раздается предупреждающий звуковой сигнал, а все символы переполне- ния теряются. В BIOS ПЗУ предусмотрен дополнительный модуль, активизирующийся прерыванием 10H, что позволяет тестировать состояние клавиатуры, опре- делять, есть ли в буфере символы, введенные с опережением, и очистить буфер. (См.: "Приложение О: Вызовы BIOS IBM PC"). Однако, использова- ние этого прерывания приводит к зависимости прикладной программы от аппаратуры, и поэтому его применение следует ограничивать. Со стороны IBM PC запрос на ввод с клавиатуры устройства CON к BIOS представляет собой просто программный код обращения к прерыванию 16H BIOS ПЗУ на выполнение аппаратно-зависимых действий. Таким обра- зом, запрос прикладной программы на ввод с клавиатуры имеет два уровня отображения: прерывание 21H преобразуется ядром MS-DOS в обращение к драйверу CON, который в свою очередь преобразует запрос в обращение к BIOS ПЗУ для получения символа. 3.3.2. Примеры программирования клавиатуры Пример: Использовать драйвер клавиатуры BIOS ПЗУ для чтения сим- вола, введенного с клавиатуры,без вывода его на дисплей: mov ah,00h ; подфункция 00H для чтения символа int 16h ; передать запрос в BIOS ПЗУ ; теперь в AH - скан-код, в AL - символ Пример: Использовать традиционную функцию MS-DOS чтения символа, введенного с клавиатуры. Символ не отображается на дисп- лее. Ввод может быть прерван по Ctrl-C: mov ah,08h ; подфункция 08H ввода символа без эхо-отображе- ; ния int 21h ; передать запрос в MS-DOS ; в AL - символ Пример: Использовать традиционную функцию MS-DOS буферизованного ввода с клавиатуры для чтения всей строки, специфицируя 80 как максимальный размер строки. Все клавиши редакти- рования используются при вводе, осуществляется эхо-отоб- ражение: kbuf db 80 ; максимальная длина входной строки db 0 ; начальная длина входной строки db 80 dup (0); буфер ввода с клавиатуры . . . mov dx,seg kbuf ; установить DS:DX = адрес mov ds,dx ; буфера ввода с клавиатуры mov dx,offset kbuf mov ah,0ah ; подфункция 0AH для чтения строки int 21h ; передать запрос в MS-DOS ; конец ввода по CR (Carriadge Return) - "Возв- ; рат каретки" (клавиша "Ввод") или достижения ; максимальной длины. Реальная длина без в ; kbuf+1 Пример: Использовать основанную на системном идентификаторе фун- кцию чтения файла или устройства (Read File или Device) и стандартный идентификатор для чтения всей строки с клавиатуры, специфицируя максимальную длину строки в 80 байтов. Все клавиши редактирования используются в про- цессе ввода. Происходит эхо-отображение. (Ввод не будет прекращен по , как ожидается, если установлен режим сквозного обмена для устройства стандартного ввода). kbuf db 80 dup(0) ; буфер ввода с клавиатуры . . . mov dx,seg kbuf ; установить DS:DX = адрес mov ds,dx ; буфера ввода с клавиатуры mov dx,offset kbuf mov cx,80 ; cx = максимальная длина строки mov bx,0 ; стандартный идентификатор - 0 mov ah,3fh ; функция 3FH для чтения файла/устройства int 21h ; передать запрос в MS-DOS jc error ; переход в случае неудачи,иначе Ax = длина вве- ; денной строки, включая и - "новая ; строка"; данные - в kbuf 3.4. Дисплей Видеодисплей является компонентой вывода данных логического сим- вольного устройства CON в MS-DOS. В компьютерах, совместимых с IBM PC, имеется несколько разновидностей поставляемых видеоподсистем. В IBM различают пять видеоподсистем, поддерживающих различные типы дисплеев: монохромный адаптер (Monochrome Display Adapter - MDA), цветной графи- ческий адаптер (Color Graphics Adapter - CGA), расширенный графический адаптер (Enchanced Graphic Adapter - EGA), видеографическая матрица (Video Graphics Array - VGA) и многоцветная графическая матрица (Multi-Color Graphics Array - MCGA). В числе несовместимых с IBM виде- оподсистем следует упомянуть графический контроллер "Геркулес" (Hercules Graphics Card) и его разновидности, поддерживающие загружае- мые шрифты. Есть два пути написания мобильных программ для видеодисплеев с использованием функций MS-DOS. Традиционный способ орбеспечивается прерыванием 21H (функция 02H - символьный вывод, 06H - прямой ввод/вы- вод на консоль, 09H - отобразить строку). Способ, основанный на ис- пользовании системного идентификатора, базируется на функции 40H (пи- сать в файл или на устройство) и доступен только начиная с версии 2.0 MS-DOS. (См.: "Системные обращения: Прерывание 21H: Функции 02H, 06H, 09H и 40H".) Во всех этих функциях дисплей рассматривается как теле- тайп; точечная графика (bit-mapped) не поддерживается. Традиционные функции 02H и 06H сходны. При обращении к ним отоб- ражаемый символ должен быть загружен в регистр DL. Отличие состоит в реакции 02H на Ctrl-C (06H не реагирует) и в невозможности вывода сим- вола 0FFH (стирание экрана в ASCII) с помощью 06H. Обе функции отраба- тывают символы 0DH (возврат каретки - carriage return), 0AH (перевод строки - linefeed) и 08H (стирание предыдущего символа - backspace). В связи с тем, что посимвольный вывод неэффективен, предпочти- тельнее использовать функцию 09H вывода строк. Для этого необходимо функции 09H указать адрес строки, которая должна заканчиваться симво- лом $. Строка, независимо от ее длины, будет выведена на экран за одну операцию. В строку могут встраиваться управляющие символы, такие как "возврат каретки" и "новая строка". При способе, основанном на системном идентификаторе, необходимо воспользоваться функцией 40H. Обычно используют назначенный стандарт- ному выводу идентификатор (равный 1), что обеспечивает поддержку пере- адресации с помощью командной строки. Если в программе необходимо "об- мануть" переадресацию и быть уверенным в том, что вывод будет осущест- вляться на экран, то можно воспользоваться предопределенным идентифи- катором устройства вывода ошибок (равным 2) или явно открыть устройст- во CON (прерывание 21H, функция 3DH) и использовать для вывода полу- ченный идентификатор. Этот способ имеет преимущества над традиционным. Во-первых, нет необходимости завершать строку спецсимволом, так как длина указывается в команде явно, и, следовательно, можно знак $ включать в качестве символа в строку. Во-вторых, традиционные функции интерпретируются внутри MS-DOS с помощью 40H, и поэтому традиционный вывод происходит медленнее. И, наконец, этот способ подобен выводу в файл. Поэтому. предпочтение должно быть отдано данному способу, если не предполагает- ся выполнение программы в среде версий 1.х MS-DOS. 3.4.1. Управление экраном Одним из недостатков стандартного драйвера устройства CON в MS-DOS является отсутствие средств управления экраном. В драйвер не встроены поддержка перемещения курсора, очистка экрана, выбор режима вывода и тому подобное. Альтернативой стандартному драйверу, начиная с версии 2.0 MS-DOS, является драйвер ANSI.SYS. В нем есть большая часть экранных функций, используемых в приложениях, обрабатывающих текстовую информацию. Для установки драйвера в системе необходимо добавить директиву DEVICE в файл CONFIG.SYS и перегрузить систему. При исполььзовании драйвера ANSI.SYS возможно управлять перемещением курсора, получать его коорди- наты, выбирать цвет фона и изображения, очищать строку или весь экран, используя Esc-последовательности (то есть символ ASCII ESC (1BH), за которым следуют различные специфические параметры, управляющие устрой- ством стандартного вывода. (См.:"Команды пользователя: ANSI.SYS".) Программы, использующие для управления экраном средства ANSI.SYS, являются переносимыми в среде MS-DOS. Если же в программе для увеличе- ния производительности применяются возможности видеодрайвера BIOS ПЗУ или команды прямого управления устройством, то свойство мобильности, естественно, утрачивается, и при изменении конфигурации оборудования придется модифицировать тексты программ. 3.4.2. BIOS ПЗУ, роль при выводе на дисплей Видеосистема IBM PC использует гибридный способ реализации вво- да-вывода: отображение в память и обращение к порту. Определенное ко- личество адресов памяти резервируется под буфер обновления экрана, со- держащий коды символов и атрибуты отображения; позиция же курсора, ре- жим отображения и другие глобальные характеристики дисплея задаются с помощью управляющих кодов при обращении к портам ввода/вывода. В состав BIOS ПЗУ входят примитивные драйверы для видеоподсистем MDA, CGA, EGA, VGA и MCGA. Они обеспечивают следующие функции: - чтение и вывод символа с предписанным атрибутом в любую позицию экрана; - определение или установка позиции курсора; - очистка или скроллирование произвольной порции экрана; - выбор палитры, цвета фона, цвета символов и границы экрана; - определение или установка режима отображения (40-колоночный текст, 80-колоночный текст, адресуемая ко всем точкам экрана графика (all-points-addressable graphics) и т.п.; - чтение и вывод точки (pixel) в любой точке экрана. Обращение к этим функциям осуществляется с помощью прерывания 10H. (См.: "Приложение О: Вызовы BIOS IBM PC".) Часть драйверов CON и ANSI.SYS обращается к этим функциям BIOS ПЗУ для обслуживания экрана, Несовместимые с IBM видеоподсистемы должны или включать в свой состав другую BIOS ПЗУ, или должны использоваться с инстоллируемым драйвером, который обеспечивает перехват прерывания 10H и выполнение соответству- ющих функций. В приложениях, ориентированных на обработку текстов, следует по возможности избегать прямого использования функций BIOS ПЗУ или обра- щения непосредственно к аппаратуре, чтобы обеспечить максимально воз- можную переносимость программ. Однако, в связи с тем, что драйвер MS-DOS CON не поддерживает графический режим, в использующих графику приложениях приходится управлять видеоадаптером и обновлять буфер нап- рямую для увеличения скорости и улучшения качества изображения. 3.4.3. Примеры программирования дисплея Пример: использовать функцию прерывания 10H BIOS ПЗУ для вывода на экран "звездочки" в текстовом режиме. (В графическом режиме необходимо установить в регистре BL цвет симво- ла.) mov ah,0eh ; подфункция 0EH = записать символ в телетайп- ; ном режиме mov al,'*' ; AL = символ для вывода mov bh,0 ; указание страницы 0 дисплея int 10h ; передать управление видеодрайверу BIOS ПЗУ Пример: использовать традиционную функцию MS-DOS для вывода на дисплей "звездочки". Если будет обнаружен ввод Ctrl-C и установлен фильтрующий режим, MS-DOS активизирует обра- ботчик прерываний по Ctrl-C, адрес которого находится в векторе прерывания 23H. mov ah,02h ; функция 02H = вывести символ на экран mov dl,'*' ; DL = символ для вывода int 21h ; передать управление MS-DOS Пример: использовать традиционную функцию MS-DOS для вывода на дисплей строки. Вывод заканчивается при поступлении символа '$' и может быть прерван в случае ввода пользо- вателем Ctrl-C при фильтрованном режиме вывода. msg db 'Это тестовое сообщение','$' . . . mov dx,seg msg ; DS:DX - адрес сообщения для вывода на mov ds,dx ; дисплей mov dx,offset msg mov ah,09h ; функция 09H int 21h ; обратиться к MS-DOS Пример: использовать средства вывода, основанные на системном идентификаторе, и зарезерварованный для вывода стандар- тный идентификатор для вывода на дисплей строки. В слу- чае фильтрованного режима вывод может быть прерван пользователем при вводе Ctrl-C. msg db 'Это тестовое сообщение' msg_len eqn $-msg . . . mov dx,seg msg ; DS:DX - адрес сообщения для вывода на mov ds,dx ; дисплей mov dx,offset msg mov cx,msg_len ; CX = длина сообщения mov bx,1 ; BX = идентификатор стандартного вывода mov ah,40h ; функция 40H - вывод в файл/устройство int 21h ; обратиться к MS-DOS 3.5. Последовательные коммуникационные порты Начиная с версии 3.2 MS-DOS, в системе имеется встроенная поддер- жка для двух последовательных коммуникационных портов COM1 и COM2 средствами трех драйверов: AUX, COM1 и COM2. (Обычно AUX является си- нонимом (алиасом) для COM1.) При традиционном способе ввода/вывода в последовательный порт ис- пользуется прерывание 21H, функция 03H для ввода из AUX и 04H для вы- вода в AUX. В MS-DOS, начиная с версии 2.0, можно также использовать для ввода/вывода на устройство AUX средства, основанные на идентифика- торе (прерывание 21H, функции 3FH и 40H). Можно использовать предопре- деленный стандартный идентификатор (равный 3) и функции 3FH и 40H, или явно открыть устройства COM1 или COM2 с помощью прерывания 21H, функ- ция 3DH, и полученный таким образом идентификатор далее использовать в операциях ввода/вывода. Средства MS-DOS поддержки последовательных портов не всегда го- дятся для применения в приложениях, если требуется высокая производи- тельность операций ввода/вывода. Во-первых, в MS-DOS нет переносимых средств тестирования наличия портов или состояния существующих портов; если в программе открывается порт COM2, туда выводятся данные, а к системе не подключен адаптер порта, то программа попросту зависнет. Подобно этому, произойдет зависание и в случае, когда программа попы- тается прочитать из порта символ, который в порт не поступил. В MS-DOS нет традиционных средств тестирования порта на наличие символа, как и в случае клавиатуры. В MS-DOS нет также аппаратно-независимых средств установки адап- тера на нужную скорость передачи, длину слова и требуемый контроль четности. Приходится для этого обращаться к BIOS ПЗУ, или прямо на уровень аппаратуры, или возложить на пользователя обязанность по пра- вильному подключению порта с помощью команды MODE перед запуском ис- пользующего этот порт приложения. Принятая по умолчанию установка для последовательного порта обес- печивает скорость передачи 2400 бод, отсутствие контроля четности, 1 стоп-бит, 8 бит данных. (См.: "Команды пользователя: MODE".) Более серьезной проблемой, однако, является невозможность управ- ления портами с помощью прерываний. Поэтому, скажем, если выбрана ско- рость передачи 1200 бод, можно потерять передаваемые символы в случае низкой скорости их обработки из-за медлительности других устройств. (Например, очистка экрана или вывод на гибкий диск.) Обращение к BIOS ПЗУ прерыванием 14H в обход MS-DOS не улучшает ситуацию, поскольку драйвер BIOS ПЗУ также не может быть прерван во время запущенной опе- рации. Ввиду описанного выше, для приложений, работающих с последова- тельными портами, приходится писать свой собственный обработчик преры- ваний, заниматься буферизацией ввода/вывода и управлять работой пор- тов. (См.: "Программирование в среде MS-DOS: коммуникация, управляемая прерываниями.") 3.5.1. Примеры программирования последовательного порта Пример: вывести строку в COM1, используя драйвер последовательно- го порта BIOS ПЗУ. msg db 'Это тестовое сообщение' msg_len eqn $-msg . . . mov bx,seg msg ; DS:BX - адрес сообщения mov ds,bx mov bx,offset msg mov cx,msg_len ; CX = длина сообщения mov dx,0 ; DX=0 для COM1 L1: mov al,[bx] ; поместить в AL следующий символ mov ah,01h ; подфункция вывода 01H int 14h ; передать управление BIOS ПЗУ inc bx ; переместить указатель на следующий ; символ loop L1 ; цикл Пример: использовать для вывода строки в COM1 традиционную функ- цию MS-DOS вывода на вспомогательное устройство. msg db 'Это тестовое сообщение' msg_len eqn $-msg . . . mov bx,seg msg ; DS:BX - адрес сообщения mov ds,bx mov bx,offset msg mov cx,msg_len ; CX = длина сообщения L1: mov dl,[bx] ; загрузить в DL следующий символ mov ah,04h ; функция вывода 04H int 21h ; передать управление MS-DOS inc bx ; переместить указатель на следующий ; символ loop L1 ; цикл Пример: для вывода строки в COM1 использовать идентификатор, предопределенный стандартному вспомогательному устройс- тву, и функцию вывода MS-DOS, базирующуюся на системном идентификаторе msg db 'Это тестовое сообщение' msg_len eqn $-msg . . . mov dx,seg msg ; DS:DX - адрес сообщения mov ds,dx mov dx,offset msg mov cx,msg_len ; CX = длина сообщения mov bx,3 ; DX - идентификатор стандартного ; устройства AUX mov ah,40h ; функция вывода 40H int 21h ; передать управление MS-DOS jc error ; переход в случае ошибки вывода 3.6. Параллельный порт и печатающее устройство Большая часть инстолляций MS-DOS включает драйверы четырех уст- ройств печати: LPT1, LPT2, LPT3, PRN. Обычно PRN является синонимом для LPT1 и соответствует параллельному системному порту вывода. Для устройств печати, не имеющих параллельного интерфейса, можно переназ- начить устройство LPT1 на один из последовательных портов с помощью команды MODE. (См.: "Команды пользователя: MODE".) Как и в случаях клавиатуры, дисплея и последовательного порта, MS-DOS предлагает для доступа к порту средства традиционные и базирую- щиеся на системном идентификаторе. При традиционном способе по преры- ванию 21H (функция 05H) находящийся в регистре DL символ посылается на устройство печати, назначенное в момент операции вывода логическому устройству LPT1. В случае метода, основанного на идентификаторах, для вывода на печатающее устройство используется прерывание 21H, функция 40H (вывод в файл или на устройство). Для вывода строк на LPT1 можно использовать предопределенный стандартный идентификтор печати (равный 4). Можно также открыть устройство печати (прерывание 21H, функция 3DH) и ис- пользовать в операциях вывода идентификатор, возвращаемый операцией открытия. Этот способ позволяет использовать одновременно несколько устройств печати. В связи с тем, что параллельные порты могут использоваться только для вывода, в MS-DOS нет традиционных средств ввода из параллельного порта. Более того, в MS-DOS нет независимых от аппаратуры средств для опроса и тестирования порта печати. Поэтому если необходимо избежать ситуации, когда символ посылается на отсутствующее или не готовое к работе устройство, следует для тестирования адаптера обращаться к BIOS ПЗУ с помощью прерывания 17H (см.: "Приложение О: Программные обраще- ния к BIOS ПЗУ IBM PC) или тестировать аппаратуру непосредственно. 3.6.1. Примеры программирования параллельного порта Пример: использовать драйвер печати BIOS ПЗУ для вывода строки в первый параллельный порт печати msg db 'Это тестовое сообщение' msg_len eqn $-msg . . . mov bx,seg msg ; DS:BX - адрес сообщения mov ds,bx mov bx,offset msg mov cx,msg_len ; CX = длина сообщения mov dx,0 ; DX=0 для COM1 L1: mov al,[bx] ; поместить в AL символ mov ah,00h ; подфункция вывода 00H int 17h ; обратиться к BIOS ПЗУ inc bx ; переместить указатель на следующий ; символ loop L1 ; цикл Пример: вывести строку в первый переллельный порт печати, ис- пользуя традиционные средства MS-DOS msg db 'Это тестовое сообщение' msg_len eqn $-msg . . . mov bx,seg msg ; DS:BX - адрес сообщения mov ds,bx mov bx,offset msg mov cx,msg_len ; CX = длина сообщения L1: mov dl,[bx] ; поместить в DL следующий символ mov ah,05h ; функция вывода на печать 05H int 21h ; передать управление MS-DOS inc bx ; переместить указатель на следующий ; символ loop L1 ; цикл Пример: вывести строку на устройство печати, используя основан- ный на идентификаторе способ и стандартный предопреде- ленный идентификатор печати. msg db 'Это сообщение печати' msg_len eqn $-msg . . . mov dx,seg msg ; DS:DX - адрес сообщения mov ds,dx mov dx,offset msg mov cx,msg_len ; CX = длина сообщения mov bx,4 ; BX - стандартный идентификатор ; для LPT1 mov ah,40h ; функция вывода 40H int 21h ; передать управление MS-DOS jc error ; переход по ошибке операции 4. Управление вводом-выводом (IOCTL) Начиная с версии 2.0 в MS-DOS обеспечивается возможность прямого взаимодействия с драйвером устройства с помощью группы подфункций пре- рывания 21H, функция 44H (IOCTL). (См.:"Системные обращения: Прерыва- ние 21H: Функция 44H".) Подфункции IOCTL включают средства, которые применимы к символьному вводу/выводу: ________________________________________________________________ Подфункция Наименование ________________________________________________________________ 00H Получить информацию об устройстве 01H Установить состояние устройства 02H Получить управляющие данные символьного устройства 03H Послать управляющие данные на символьное устройство 06H Проверить состояние ввода 07H Проверить состояние вывода 0AH Проверить, является ли системный идентификатор уда- ленным (версии 3.1 и последующие) 0CH Генерировать управляющие символы ввода/вывода для идентификатора: получить/установить счетчик итера- ций вывода ________________________________________________________________ Подфункция 00H возвращает слово состояния устройства, тестируя которое можно установить, какой идентификатор связан с устройством или файлом, и может ли драйвер устройства обрабатывать управляющие после- довательности символов, посылаемые подфункциями 02H, 03H. Можно также определить, является символьное устройство стандартным вводом, выво- дом, CLOCK$, NUL и какой режим - прозрачный или фильтрованный - уста- новлен. Далее с помощью подфункции 01H можно установить требуемый ре- жим для последующего ввода/вывода, основанного на системных идентифи- каторах. Подфункции 02H и 03H позволяют обмениваться с устройством управ- ляющими последовательностями символов, которые обычно не влияют на са- мо физическое устройство. Например, обычный драйвер устройства позво- ляет установить нужную конфигурацию последовательного порта с помощью подфункции 03H, задавая специальную управляющую последовательность. Аналогично, драйвер в ответ на подфункцию 02H возвращает последова- тельность символов, которая характеризует конфигурацию и состояние последовательного порта. Подфункции 06H и 07H можно использовать для тестирования готов- ности устройства для ввода/вывода. В частности, они применимы к после- довательным портам и параллельным портам вывода, поскольку MS-DOS не обеспечивает традиционные функции тестирования состояния портов. Подфункция 0AH позволяет определить, является ли идентификатор устройства локальным или удаленным, то есть выполняется ли программа на удаленном компьютере локальной сети. В принципе, для проведения са- мих операций ввода/вывода информация об удаленности не имеет никакого значения,- она нужна только для страховки от возникновения ошибочных ситуаций. Эта подфункция доступна только в среде Microsoft Networks. И, наконец, подфункция 0CH позволяет определять или устанавливать количество попыток обращения к устройству печати, чтобы принять реше- ние о неготовности/недоступности устройства. 4.1. Примеры программирования с использованием IOCTL Пример: использовать подфункцию 00H IOCTL для получения и сохра- нения слова состояния устройства, соответствующего стан- дартному идентификатору ввода; затем использовать под- функцию 01H для перевода стандартного ввода в прозрачный режим. info dw ? ; для слова состояния устройства . . . mov ax,4400h ; AH - функция 44H; AL - подфункция 00H mov bx,0 ; BX - идентификатор стандартного ввода int 21h ; обратиться к MS-DOS mov info,dx ; сохранить слово состояния or dl,20h ; взвести бит прозрачного режима mov dh,0 ; очистить DH (по требованию MS-DOS) mov ax,4401h ; AL - подфункция 01H int 21h ; обратиться к MS-DOS Пример: использовать подфункцию 06H IOCTL для тестирования пер- вого последовательного порта, поступил ли символ ввода. Функция возвращает AL=0FFH, если да, и AL=00H, если нет. mov ax,4406h ; AH - функция 44H; AL - подфункция 06H mov bx,3 ; BX - стандартный идентификатор устройства AUX int 21h ; обратиться к MS-DOS or al,al ; тестировать состояние устройства jnz ready ; переход - символ есть, иначе - ожидать Джим Кайл, Чип Рабинович Глава 6. Управление коммуникациями на основе прерываний Совсем недавно, когда линии связей у персональных копьютеров(ПК) имели небольшую скорость (ниже 300 бит в секунду) наличие простых программ, обеспечивающих передачу символов между удаленными устройст- вами, было оправдано. ПК имел достаточно времени между пересылаемыми по линиям связи символами, чтобы определить необходимую последователь- ность действий. Современный уровень передачи данных в четыре, а то и в восемь раз быстрее, и поэтому между передаваемыми символами остается мало времени для обработки. Уже при скорости 1200 бит/c возможна потеря по крайней мере трех символов за то время, которое необходимо для скроллинга изображения на дисплее на одну строку вверх. При таких скоростях ста- новится необходимой возможность одновременного получения и отображения символов на дисплее. Большие ЭВМ для координации подобных действий используют аппарат- ную систему прерываний. Суть ее состоит в том, что для переключения внимания процессора к требующему обслуживания периферийному устройству последнее посылает к процессору запрос, вызывающий прерывание. Процес- сор прерывает выполнение обычной работы, обслуживает устройство и пос- ле этого возвращается к выполнению прерванной работы. Такой тип обра- ботки известен как управление на основе прерываний. Он позволяет вы- полнить два дела одновременно и не требует для этой цели двух раздель- ных процессоров. Для обеспечения успешного уровня телекоммуникации в персональных компьютерах необходимо наличие программы обработки прерываний для по- лучения данных. В данной главе обсуждается в деталях техника обработки прерываний и приводится два простых текста программ. Глава начинается с установления цели программ коммуникации, а за- тем в ней обсуждаются возможности простых функций, предоставляемых MS DOS, предназначенных для достижения этой цели. Для определения того, что необходимо для реализации функций MS-DOS, обсуждаются особенности аппаратуры (модемов и последовательных портов). Это приводит к обсуж- дению принятого в MS-DOS, начиная с версии 2.0, такого специфичного интерфейса, как устанавливаемые драйверы устройств. Дополнительно обсуждаются вопросы, связанные с обработкой преры- ваний на основе техники рекомендуемой MS-DOS, с одной стороны, и про- мышленными стандартами, с другой стороны. Для обоих этих случаев при- водятся примеры программ. Все приводимые в этой главе обсуждения учитывают архитектурные ограничения и возможности системы BIOS семейства IBM-подобных компь- ютеров. Системы MS-DOS, не полностью совместимые с этой архитектурой, на более низком уровне детализации могут использовать при реализации совершенно отличные подходы , но в общем они используют те же основные принципы обработки прерываний. 1. Цель создания программ коммуникаций Основная задача таких программ состоит в осуществлении коммуника- ции, то есть в передаче введенной с клавиатуры или прочитанной из фай- ла информации, в подходящей для этой цели форме, к удаленному компь- ютеру по телефонным линиям связи и, наоборот, преобразование получен- ной от удаленного компьютера информации и отображение ее на экране ви- деодисплея или занесение ее в некоторый файл. Раньше большинство программ коммуникации дублировали модемное ус- тройство по аналогии с аналитическими устройствами Бэббиджа, или моде- лями устройства вывода, используемыми в разработках по искусственному интеллекту. Функции модемного устройства являются общими для всех ви- дов программ коммуникации от самых простых до самых сложных и могут быть представлены с помощью псевдо-С следующим образом: The Modem Engine Pseudocode DO { IF (input character is available) ;если доступен входной символ send_it_to_remote; ;послать его на удаленный узел IF (remote character is available) ;если доступен символ, посту- ;пивший из удаленного узла, use_it_locally; ;использовать его локально } UNTIL (told_to_stop); ;останов Смысл этой псевдо-программы состоит в том, что если отсутствует передаваемый символ или символ, пришедший от удаленного устройства, то программа не переходит в состояние ожидания, а продолжает свою работу. Если при выполнении цикла обнаруживается работа для выполнения, то она выполняется, если нет, то циклический опрос продолжается. В действительности, иногда предпочтительнее остановить работу мо- демного устройства. Например, при получении длинного сообщения жела- тельно иметь возможность сделать паузу и прочитать это сообщение перед тем, как исчезнут строки на экране в результате скроллинга. С другой стороны, если для просмотра экрана требуется много времени, то следую- щие поступающие символы будут теряться. Для предотвращения этого ис- пользуется метод, называемый "управление потоком". Этот метод исполь- зует специальный управляющий символ, который посылается устройству для приостановки передачи данных, и другой управляющий символ, посылаемый устройству позже, для продолжения прерванной передачи данных. Существуют некоторые соглашения по управлению потоком. Самое рас- пространенное имеет название XON/XOFF, получившее свое название по имени двух управляющих кодов протокола Teletype-33. В первозданном ви- де код XOFF останавливал бумажную ленту телетайпа, а код XON возобнов- лял ее продвижение. В середине 1967 года фирма General Electric начала использовать эти сигналы в системах разделения времени для управления потоком данных, и довольно быстро этот метод распространился в промыш- ленности. Простой пример программы ENGINE, приводимый в дальнейшем в этой главе, почти буквально повторяет принцип работы модемного устройства. Этот пример демонстрирует исключительную простоту коммуникационных программ. Другой пример программы CTERM.C является более сложным, но и в нем используется принцип работы модемного устройства. 2. Использование простых функций MS-DOS Поскольку среди стандартных обслуживающих функций MS-DOS имеются такие, которые обеспечивают ввод/вывод данных от устройства AUX (по умолчанию устройство СОМ1, подключенное в большинстве ЭВМ к последова- тельному порту), то первоначальная попытка моделирования модема с по- мощью функций MS-DOS может иметь вид, подобный приводимому ниже фраг- менту программы на Microsoft Macro Assembler (MASM): ;Неполный (и не работоспособный) фрагмент программы LOOP: MOV AH,08H ;читать с клавиатуры без эхо-отображения INT 21H MOV DL,AL ;установить режим пересылки MOV AH,04H ;послать на устройство AUX INT 21H MOV AH,03H ;читать из AUX INT 21H MOV DL,AL ;установить режим пересылки MOV AH,02H ;послать на экран INT 21H YMP LOOP ;не прекращать этот цикл Вышеприведенный фрагмент программы имеет два существенных недос- татка. Во-первых, прерывание 21H с функцией 08H не будет возвращать управление программе до тех пор, пока не будет нажата клавиша на кла- виатуре и поэтому нельзя будет считывать данные с устройства AUX. Ана- логично, функция 03H переводит программу в состояние ожидания до тех пор, пока ей не станет доступен символ от устройства AUX, т.е. никакие ключи не могут быть распознаны пока удаленное устройство не пошлет символ. Если ничего не будет получено, то цикл навсегда останется в состоянии ожидания. Чтобы обойти недостаток, связанный с взаимодействием с клавиату- рой, можно использовать функцию 0BH, чтобы сначала выяснить была нажа- та клавиша на клавиатуре, прежде чем начинать чтение с нее. Сказанное отражено в приводимом ниже измененном фрагменте программы: ;Улучшенный(но все еще не работоспособный) фрагмент программы LOOP: MOV AH,0BH ;тестировать клавиатуру, нажата ли клавиша INT 21H OR AL,AL ;тест на 0 YZ RMT ;не нажата клавиша, пропустить обработку MOV AH,08h ;клавиша нажата, прочитать символ INT 21H MOV DL,AL ;установить режим пересылки MOV AH,04h ;послать на устройство AUX INT 21H RMT: MOV AH,03H ;читать из AUX INT 21H MOV DL,AL ;установить режим пересылки MOV AH,02h ;послать на экран INT 21H YMP LOOP ;не прекращать этот цикл Эта программа уже позволяет распознавать любой ввод от устройства AUX, не ожидая нажатия клавиш на клавиатуре . Следует однако отметить, что если устройство AUX медленное, то программа неопределенное коли- чество времени будет находиться в состоянии ожидания до момента пере- хода к дальнейшей работе с клавиатурой. Таким образом, указанные ранее недостатки решены только частично. Однако система MS-DOS не имеет прямых методов тестирования уст- ройства AUX или, что по существу то же самое, устройств, подключенных к последовательному порту. Вот почему программы коммуникации отличают- ся от большинства других типов программ, работающих под управлением MS DOS и вот почему эти программы должны учитывать специфику работы уст- ройств, пренебрегая принципами переносимости программных продуктов. 3. Используемые аппаратные средства Для коммуникации в персональных компьютерах требуется по крайней мере два различных устройства (даже если они скомпонованы на одной плате). Это последовательный порт, который преобразует данные с внут- ренней шины в битовый поток и передает их по внешней линии, и модем, который преобразует битовый поток в подходящую форму для передачи по телефонной линии (или иногда по радиоканалу). 3.1. Модем Модем (слово происходит от модулятор-демодулятор) - это такое ус- тройство, которое преобразует поток битов, представляющий собой после- довательность переменных уровней напряжения, в звуковые частотные сиг- налы, подходящие для передачи по телефонным линиям (модуляция) и пос- ледующее преобразование этих сигналов обратно в поток битов, который идентичен исходному (демодуляция). Специфические характеристики используемых звуковых частотных сиг- налов были установлены фирмой AT&T в период монопольного производства этой фирмой модемных устройств. Эти характеристики стали стандартом де-факто даже после того, как фирма утратила свою монополию на произ- водство модемов. Эти характеристики принимают различные формы, завися- щие от используемой скорости передачи данных. Формы обычно идентифици- руются уникальным номером, установленным фирмой Bell, так например 103 (для передачи данных в 600 бит в секунду и ниже) или 212А (для переда- чи данных со скоростью 1200 бит/секунду). Скорость передачи данных измеряется в битах за секунду (bps - bits per second) и часто неправильно трактуется как бод или "бод за секунду". В действительности бод определяет количество сигналов за се- кунду. Если одно изменение сигнала соответствует одному биту, что со- ответствует стандарту 103 фирмы Bell, то единицы бод и bps эквивалент- ны. Но эти величины в более сложных сигналах не равны между собой. Например, в двухфазном стандарте 212А, соответствующем 1200 bps, ис- пользуется двухмодуляционный поток (каждый по 600 бод) для передачи данных со скоростью 1200 бит в секунду. Для общей строгости в этой главе используется единица bps, кроме тех случаев, когда для широкораспространенных устройств неправильное использование единицы "бод" стало стандартом. Первоначально модемное устройство представляло собой блок, подсо- единенный кабелем через последовательный порт к компьютеру. Характе- ристики этого кабеля, его разъемы и сигналы были стандартизованы в 1960 году фирмой Electronic Industries Association (EIA) в стандарте RS232C. Подобно стандартам для модемов фирмы Bell, стандарт RS232C почти не претерпел изменений. Его характеристики приведены в таблице 6-1. С ростом популярности персональных компьютеров модемное устройст- во было объединено с последовательным портом и размещено на генмонтаж- ной плате. Первоначально такие устройства выпускала фирма Hayes Corporation, которая подобно фирмам Bell и EIA, создала некоторый стандарт. Функци- онально такой внутренний модем идентичен комбинации "последовательный порт - кабель - внешний модем". Таблица 6-1 Сигналы стандарта RS232C ___________________________________________________________________ | | | | | | DB25 Pin | 232 | Имя | Описание | |__________|___________|___________|________________________________| | | | | | | 1 | | | | | 2 | BA | TXD | | | 3 | BB | RXD | Передать данные | | 4 | CA | RTS | Получить данные | | 5 | CB | CTS | Запрос передатчика | | 6 | CC | DSR | Сброс передатчика | | 7 | AB | GND | Готовность модема | | | | | | | 8 | CF | DCD | Детектор носителя данных | | 20 | CD | DTR | Готовность терминала | | 22 | CE | RI | Индикатор шлейфа | |___________________________________________________________________| 3.2. Последовательный порт Любой последовательный порт стандарта IBM PC соединяет систему с устройством INS8250 интегральной схемой (чипом) универсального асинх- ронного приемо-передатчика (Univercal Asynchronous Receiver Transmitter - UART), разработанной фирмой National Semiconductor Corporation. Этот чип, совместно со схемой, объединенные в порту: 1. Преобразует данные, получаемые с системной шины, в последова- тельность уровней напряжения по выходной линии TXD, которая представ- ляет двоичные цифры. 2. Преобразует данные получаемые в виде последовательности двоич- ных уровней на входной линии RXD, в байты для системной шины. 3. Управляет действиями модема посредством выходных линий DTR и RTS. 4. Предоставляет процессору информацию о текущем состоянии; эта информация поступает от модема по входным линиям DSR, CTS и RI от чипа UART и указывает на доступность данных, на запрос данных, на обнару- женные ошибки. Слово "асинхронный" в названии чипа идет от спецификаций, уста- новленных фирмой Bell. При передаче данных каждый бит, связанный с со- седними битами, должен быть сохранен. Этого можно добиться двумя путя- ми. Самый очевидный способ состоит в синхронизации битового потока с заданной частотой таймера и подсчете циклов для идентификации битов. Такой метод передачи известен под названием синхронный, также исполь- зуются сокращения "synch" или же "bisync" для двоичной синхронизации. Во втором методе (первоначально был использован в механизмах телеприн- тера) начало группы битов определяется с помощью стартбита и конец с помощью одного или большего числа стоп-битов. В этом методе время пе- редачи одного бита определено. Старт-бит отмечает начало получаемой группы; в дальнейшем сигнал выбирается за время передачи одного бита, пока не встретится стоп-бит. Это асинхронный метод (часто используется термин "asynch"), и он был использован в стандарте IBM PC. Старт-бит, по определению, это то же самое, что и двоичный ноль, а стоп-бит - двоичная единица. Нулевой сигнал обычно называют SPACE, а единичный - MARK, по аналогии с терминами, используемыми в телеприн- терной индустрии. Во время передачи самый последний бит посылается первым после старт-бита. Если используется бит четности, то он появляется как самый старший бит в группе данных перед стоп-битом (стоп-битами). Этот бит ничем не отличается от битов данных, кроме своего расположения. Как только посылается стоп-бит, линия передачи переходит в состояние MARK (иногда называемое "idling" - не работающее) до тех пор, пока новый старт-бит не укажет на начало новой группы данных. В большинстве используемых персональных компьютеров последова- тельный порт преобразует один 8-битный байт за раз и термин "слово" соответствует 16 битам. Для устройств UART, однако, термин "слово" обозначает единицу информации, посылаемую чипом в каждом фрагменте (chunk). Длина слова составляет часть управляющей информации и опреде- ляется в чипе во время операции установки и может быть 5, 6, 7 или 8 бит. В данном тексте при употреблении термина "слово" мы придерживаем- ся соглашений, принятых в устройствах UART. Одним специальным сигналом, почти не используемым при коммуника- ции "компьютер-компьютер", но иногда необходимым при коммуникации с большой ЭВМ, является сигнал BREAK. Сигнал BREAK является сигналом SPACE, расширенным по времени более чем на время передачи одного сло- ва, включая время передачи стоп-бита. (Многие системы требуют чтобы длительность сигнала BREAK была не меньше 150 мсек, несмотря на уро- вень передачи данных). Поскольку этот сигнал не может быть сгенериро- ван посредством обычной передачи символа, то для сигнала BREAK необхо- димо прерывать обычное выполнение операций. Устройство 8250 UART IBM PC может генерировать сигнал BREAK, но его длительность должна быть установлена некоторой программой, а не чипом. 3.3. Архитектура устройства 8250 UART Устройство 8250 UART состоит из четырех основных частей: прием- ник, передатчик, схемы контроля и схемы состояния. Поскольку эти части тесно связаны, то перед тем, как перейти к следующим подпараграфам, необходимо определить некоторые термины. Основными элементами приемника являются регистр сдвига и регистр данных, называемый Received Data Register. Регистр сдвига собирает последовательно получаемые данные в форму слова путем сдвига сигналов на линии RXD в младший бит регистра, причем предыдущие биты сдвигаются дальше. Когда регистр сдвига заполняется, находящиеся в нем биты пере- писываются в регистр данных, регистр сдвига обнуляется , а в схеме состояния утснавливается бит, сигнализирующий о том, что данные приня- ты. В случае обнаружения ошибки в процессе приема этого слова в схеме состояния устанавливаются соответствующие биты. Аналогично, основными элементами передатчика являются регистр сохранения (Transmit Holding Register) и регистр сдвига. Каждое пере- даваемое слово выбирается с шины данных и помещается в регистр сохра- нения. Если при выполнении этого действия регистр сохранения не пус- той, то находящиеся в нем данные теряются. Регистр сдвига преобразует слово данных в форму последовательных битов, пригодную для передачи. Это происходит следующим образом: из регистра на линию TXD выталкива- ется старший бит, одновременно младшие биты сдвигаются на его место и обнуляется самый младший бит. Когда на линию TXD будет вытолкнут пос- ледний бит, содержимое регистра сохранения переписывается в регистр сдвига, а регистр сохранения обнуляется; если на шине данных нет сле- дующих данных для передачи, в схеме состояния устанавливается бит, указывающий на то, что регистр сохранения пуст и что все готово для передачи нового слова. Бит четности, если нужно, и стоп-биты добавля- ются к передаваемому потоку после последнего вытолкнутого бита слова. Схемы контроля определяют три коммуникационные характеристики: во-первых, значения величин линии управления(длина слова; установлен или нет контроль по четности; количество стоп-битов); во-вторых, зна- чения величин для управления модемом (установка DTR и RTS на выходных линиях); в-третьих, скорость приемапередачи данных. Эти управляющие величины устанавливаются с помощью двух 8-битовых и одного 16-битного регистров, которые могут быть доступны как четыре 8-битных регистра: LCR (регистр линии управления - Line Control Register), MCR ( регистр управления модемом - Modem Control Register), 16-битный BRG, к которо- му можно адресоваться как к Baud0 и Baud1. Установка скорости передачи данных на регистр BRG осуществляется с помощью программируемого генератора скорости передачи данных (Programmable Baud Rate Generator - PBRG), который является основной частью управляющих схем. PBRG может устанавливать любую скорость пере- дачи данных, начиная от нескольких бит в секунду до 38400 bps. В сис- темах BIOS для IBM PC, PC/XT и PC/AT поддерживаются только уровни от 110 до 9600 bps. Далее будет рассмотрено каким образом устанавливаются значения в регистрах LRC и MCR и каким образом программируется PBRG. Четвертым основным элементом устройства 8250 UART являются схемы состояния, которые регистрируют (в парных регистрах состояния) состоя- ния в приемнике и передатчике, любые возникающие ошибки и изменения в состоянии входных от модема линиях стандарта RS232C. При любом измене- нии содержимого регистров состояния, если разрешено, генерируется зап- рос на прерывание системы. Такой подход позволяет ЭВМ выполнять неко- торую другую работу без постоянной проверки состояния последовательно- го порта и, если это состояние измениться немедленно выполнить соот- ветствующие действия. 3.3.1. Программный интерфейс устройства 8250 Не все регистры, упоминавшиеся в предыдущем параграфе, доступны программно. Например, регистры сдвигов доступны только внутренним схе- мам устройства 8250. Программно доступны 10 регистров и доступ к ним осуществляется только через семь различных адресов (см. табл.6-2). Ре- гистр данных (Receive Data Register) и регистр сохранения (Transmit Holding Register) разделяют один адрес (чтение полученных данных; за- пись в регистр сохранения). Дополнительно, оба эти адреса и регистр разрешения прерывания (Interrupt Enable Register - IER) разделяются с устройством PBRG. Значения бита в регистре LCR, имеющего название Divisor Latch Access Bit (DLAB), определяет в каждый момент времени какие регистры доступны. В IBM PC эти семь адресов, используемые устройством 8250, опреде- ляются с помощью 3-х младших битов номера порта (старшие биты опреде- ляют конкретный порт). Таким образом каждый последовательный пот зани- мает восемь позиций в адресном пространстве. Однако используются толь- ко младшие адреса, в одном из которых все три младших бита равны нулю. Это необходимо помнить, чтобы обращаться по всем восьми адресам. В связи с этим, для ссылок к любому последовательному порту по адресу, этот адрес в шестнадцатиричной нотации должен заканчиваться либо на 0 либо на 8. Обычно порт СOM1 использует адрес 03F8H, а COM2 - 02F8H. Эти адреса называются базовыми адресами порта и каждый адресуе- мый регистр определяется смещением относительно этого базового адреса (см. табл. 6-2) Таблица 6-2 Смещения относительно базового адреса порта ____________________________________________________________________ | | | | | Смещение | Имя | Описание | |__________|___________|_____________________________________________| | | | | | Если бит DLAB в регистре LCR = 0 | | 00H | DATA | Если операция чтения, то регистр данных; | | | | если операция записи, то регистр сохранения;| | 01H | IER | регистр разрешения прерывания | | Если бит DLAB в регистре LCR = 1 | | 00H | Baud 0 | младший байт регистра BRG | | 01H | Baud 1 | старший байт регистра BRG | | Не зависит от значения бита DLAB | | 02H | IID | регистр идентификации прерывания | | 03H | LCR | регистр линии управления | | 04H | MCR | регистр управления модемом | | 05H | LSR | регистр линии состояния | | 06H | MSR | регистр состояния модема | |__________|___________|_____________________________________________| 3.3.2. Схемы управления Схемы управления устройства 8250 включают программируемый генера- тор скорости передачи данных (PBRG), регистр управления линией (LCR), регистр управления модемом (MCR), и регистр разрешения прерывания (IER). Устройство PBRG устанавливает время передачи одного бита, исполь- зуемое при приеме/передаче данных, посредством деления сигнала врешне- го таймера. Чтобы выбрать желаемый уровень передачи данных, соответст- вующий делитель загружается в устройство PBRG. Это делается следующим образом: бит DLAB устанавливается в 1 и делитель заносится в регистры Baud 0 и Baud 1; затем бит DLAB опять устанавливается в 0, чтобы обес- печить нормальную работу с регистром данных и IER. В таблице 6-3 приведен список значений делителя, обеспечивающих уровень передачи данных в интервале 45,5 и 38400 bps для таймерного устройства с частотой 1.8432 Мгц, используемого в стандартных системах IBM. Эти уровни устанавливаются в кристалле, находящемся в последова- тельном порту (или внутри модема), и вообще не связан со скоростью процессорного таймера. Таблица 6-3 Значения делителя устройства 8250/IBM UART _________________________________________________________ | | | | бит в секунду | Делитель | |________________________________|________________________| | 45,5 | 2532 | | 50 | 2304 | | 75 | 1536 | | 110 | 1047 | | 134,5 | 857 | | 150 | 768 | | 300 | 384 | | 600 | 192 | | 1200 | 96 | | 1800 | 64 | | 2000 | 58 | | 2400 | 48 | | 4800 | 24 | | 9600 | 12 | | 19200 | 6 | | 38400 | 3 | |________________________________|________________________| В таблице 6-4 приведен список значений битов регистра LCR, кото- рые определяют назначение смещений 0 и 1 относительно базового адреса порта, передачу сигнала BREAK, паритет, количество стоп-битов и длину слова при приеме/передаче. Два бита регистра MCR (см. таблицы 6-5) управляют выходными лини- ями DTR и RTS; другие два бита этого регистра ( OUT1 и OUT2) предназ- начены для использования программистами; бит (TEST) переводит устройс- тво UART в режим самотестирования. Старшие 3 бита не воздействуют на UART. Над регистром MCR можно производить как операцию чтения, так и записи. Биты, назначенные пользователю, определяются в IBM PC. OUT1 ис- пользуется внутренними модемами фирмы Hayes, чтобы перевести схемы в начальное состояние, OUT2 управляет прохождением генерируемых устройс- твом UART сигналов, вызывающих прерывание, в персональный компьютер. Если OUT2 не установлен в 1, то сигналы прерывания от устройства UART не смогут достигнуть ЭВМ даже при установлении всех других соответст- вующих управляющих значений. Это свойство описано в руководстве по техническим средствам IBM, но описание это не совсем ясное. В связи с этим довольно часто эта особенность не учитывается программистами, создающими программу обработки прерываний для таких устройств. Таблица 6-4 Значения битов регистра LCR __________________________________________________________________ | | | | | | Бит | Имя | Двоичное | Значение | | | |представление| | |__________|________|_____________|________________________________| | | | | | |Управление| DLAB | 0XXXXXXX | Смещение 0 указывает на DATA, | |адресом | | | cмещение 1 указывает на IER | | 7 | | | | | | | | | | | | 1XXXXXXX | Смещение 0 указывает на Baud0, | | | | 0XXXXXXX | смещение 1 указывает на Baud1| | | | | | |Управление|SETBREAK| X0XXXXXX | Обычная работа устройства UART | |сигналом | | | | |BREAK, 6 | | X1XXXXXX | Послать сигнал BREAK | | | | | | |Управление| GENPAR | XXXX0XXX | бит паритета не установлен | |паритетом | | XX001XXX | паритет по нечетности (ODD) | | 5,4,3 | | XX011XXX | паритет по четности (EVEN) | | | | XX101XXX | паритет равен 1 | | | | XX111XXX | паритет равен 0 | | | | | | |Стоп-биты | XSTOP | XXXXX0XX | Один стоп-бит | | 2 | | XXXXX1XX | два стоп-бита (если WL = 5, | | | | | то 1,5) | | | | | | | Длина | WD5 | XXXXXX00 | Длина слова равна 5 | | слова | WD6 | XXXXXX01 | Длина слова равна 6 | | 1, 0 | WD7 | XXXXXX10 | Длина слова равна 7 | | | WD8 | XXXXXX11 | Длина слова равна 8 | |__________|________|_____________|________________________________| Таблица 6-5 Значения битов регистра MCR ____________________________________________________________________ | | | | | Имя | Двоичное | Описание | | |представление| | |______|_____________|_______________________________________________| | TEST | XXX1XXXX | Включить самотестирование устройства UART | | | | | | OUT2 | XXXX1XXX | Управление сигналами прерывания устройства | | | | UART | | | | | | OUT1 | XXXXX1XX | Перевести в начальное состояние встроенный мо-| | | | дем Hayes на 1200 бит/сек | | | | | | RTS | XXXXXX1X | Установить RTS на разъем RS232C | | | | | | DTR | XXXXXXX1 | Установить DTR на разъем RS232C | |______|_____________|_______________________________________________| Устройство 8250 может генерировать некоторые или все четыре типа прерываний. Разрешение или запрещение соответствующего прерывания осу- ществляется с помощью управляющего бита данного прерывания в регистре IER (см. табл. 6-6). Таким образом, если регистр IER содержит нули, то все прерывания от устройства UART запрещены независимо от того, уста- новлен или нет бит OUT2 или выставлены команды CLI/STI. Для регистра IER разрешены операции чтения и записи, но на схемы UART воздействуют только четыре младших бита регистра. Таблица 6-6 Значения битов регистра IER ____________________________________________________________________ | | | | Двоичное представление | Действия | |________________________|___________________________________________| | | | | XXXX1XXX | Разрешено прерывание на состояние модема | | XXXXX1XX | Разрешено прерывание на состояние линии | | XXXXXX1X | Разрешено прерывание по готовности данных | | | к передаче | | XXXXXXX1 | Разрешено прерывание по готовности полу- | | | ченных данных | |________________________|___________________________________________| 3.3.3. Схемы состояния Схемы состояния включают регистр состояния линии (Line Status Register - LSR), регистр состояния модема (Modem Status Register - MSR), регистр идентификации прерывания (Interrupt Indentifier Register - IID) и систему генерации прерывания. Устройство 8250 включает логические схемы , которые распознают поступление сигнала BREAK, а также три типа ошибок приема данных. В регистре LSR устанавливаются различные биты (см. табл. 6-7), чтобы указать на то, что поступил сигнал BREAK, или обнаружены следующие ошибки: ошибка парирета, ошибка оформления посылки (во время передачи стоп-бита поступающий бит равен 0), ошибка переполнения (слово еще не прочитано из буфера за время, когда следующее слово должно быть запи- сано в этот буфер). Остальные биты регистра LSR определяют состояние регистра сдвига передатчика, регистра сохранения передатчика и регистра данных прием- ника. Старший бит регистра LSR не используется и всегда равен 0. Над регистром LSR разрешена только операция чтения, попытки записать в этот регистр не воздействуют на него. Регистр MSR (см.табл. 6-8) контролирует четыре линии RS232C, по которым определяется состояние модема. Старшие 4 бита этого регистра указывают на уровень напряжения соответствующей линии RS232C; младшие Таблица 6-7 Значение битов регистра LSR ____________________________________________________________________ | | | | | Бит | Двоичное представление | Значение | |_____|________________________|_____________________________________| | | | | | 7 | 0XXXXXXX | Всегда равен нулю | | 6 | X1XXXXXX | Регистр сдвига передатчика пуст | | 5 | XX1XXXXX | Регистр сохранения передатчика пуст | | 4 | XXX1XXXX | Получен сигнал BREAK | | 3 | XXXX1XXX | Ошибка оформления посылки | | 2 | XXXXX1XX | Ошибка парирета | | 1 | XXXXXX1X | Ошибка переполнения | | 0 | XXXXXXX1 | Готовность полученных данных | |_____|________________________|_____________________________________| 4 бита указывают на изменение уровня напряжения на этих линиях с мо- мента последнего чтения этого регистра. Таблица 6-8 Значения битов регистра MSR ____________________________________________________________________ | | | | | Бит |Двоичное представление| Значение | |_____|______________________|_______________________________________| | | | | | 7 | 1XXXXXXX | Уровень детектора носителя данных(DCD)| | 6 | X1XXXXXX | Уровень индикатора шлейфа (RI) | | 5 | XX1XXXXX | Уровень готовности модема (DSR) | | 4 | XXX1XXXX | Уровень сброса передатчика (CTS) | | 3 | XXXX1XXX | Изменился DCD | | 2 | XXXXX1XX | Изменился RI | | 1 | XXXXXX1X | Изменился DSR | | 0 | XXXXXXX1 | Изменился CTS | |_____|______________________|_______________________________________| Как уже упоминалось, генерируются четыре типа прерываний. Все эти прерывания определяются значениями флагов в регистре идентификации прерываний (IID)(см.табл.6-9). Эти флаги устанавливаются следующим об- разом: Изменение любого бита в регистре MSR влечет установку флага сос- тояния модема. Установление бита получения сигнала BREAK или любых битов иденти- фикации ошибки в регистре LSR влечет установку флага состояния линии. Установление в регистре LSR бита, указывающего на то, что регистр сохранения передатчика пуст, влечет установление флага передатчика. Установление в регистре LSR бита, указывающего на то, что полу- ченные данные готовы для использования, влечет установление флага при- емника. Регистр IID идентифицирует некоторый тип прерывания, даже если это прерывания было замаскировано в регистре IER. Для регистра IID разрешена только операция чтения; попытка записи в этот регистр не воздействует на него. Таблица 6-9 Значение битов регистра IID ____________________________________________________________________ | | Содержимое IID | Значение | _________________|__________________________________________________| | | XXXXXXX1B | Нет прерываний | | | XXXXX000B | Прерывание состояния модема; в MSR изменился | | бит | | | XXXXX010B | Прерывание передачи данных; регистр сохранения | | передатчика пуст, в LSR установлен соответствую- | | щий бит | | | XXXXX100B | Прерывание приема данных; регистр данных прием- | | ника заполнен, в LSR установлен соответствующий | | бит | | | XXXXX110B | Прерывание состояния линии; получение сигнала | | BREAK или обнаружение ошибки | _________________|__________________________________________________| Как только в регистре IID будет установлен флаг, то генератор прерываний устройства UART будет, если разрешено, генерировать запрос на прерывание к процессору. В одно и тоже время могут быть активны два или более прерываний, в этом случае в регистре IID будут установлены два или более флага. В регистре IID флаг соответствующего прерывания ( и связанные с ним биты в регистрах LSR и MSR) обнуляются, когда читается соответст- вующий регистр (или же в него производится запись). Например, чтение содержимого регистра MSR очищает флаг состояния модема, запись байта в регистр данных передатчика очищает флаг передатчика; чтение регистра данных приемника очищает флаг приемника; чтение регистра LSR очищает флаг состояния линии. Биты LSR или MSR не очищаются , пока не будут прочитаны; IID флаг очищается вместе с одним из битов LSR или MSR. 3.3.4. Программирование устройства UART Каждый раз, когда включается персональный компьютер, перед ис- пользованием устройства с последовательным интерфейсом необходимо вы- полнить его программную настройку. Это можно сделать либо с помощью последовательности кодов, входящих в программу первоначальной загруз- ки, либо с помощью подпрограммы инициализации порта, входящей в инс- таллируемый драйвер порта. Часто используют оба способа. Программа первоначальной загрузки обеспечивает характеристики по умолчанию, ко- торые могут быть модифицированы для обеспечения возможностей драйвера порта, используемого при данном подключении. При программировании чипа 8250 для установления соответствующего уровня передачи данных должны быть установлены регистры устройства PBRG (Baud0 и Baud1), загружены регистры LCR и MCR, установлены соот- ветствующие биты в регистре IER и очищен буфер приемника. Последова- тельность выполняемых действий не является существенно критической, но любые ожидающие запросы на прерывания должны быть очищены до того, как они могут быть переданы к процессору. Следующий пример программы выполняет эти начальные действия: ус- танавливает чип устройства COM1 (порт 03F8H) для обработки данных с уровнем передачи в 1200 bps и длиной слова в 8 бит, без контроля на бит четности с разрешением всех типов прерываний устройства UART . (В реальной программе все значения адресов и обрабатываемых условий не будут встроены в программу; в данном примере они включены для ясности понимания выполняемых шагов). MOV DX, 03FBh ;адрес LCR (3) относит. базового адреса порта ; COM1 (03F8) MOV AL, 080h ;переключить на работу с Baud0 и Baud1 (DLAB=1) OUT DX, AL ; MOV DX, 03F8h ;адрес Baud0 MOV AX, 96 ;установить в Baud0 делитель для 1200 bps OUT DX, AL ; INC DX ;адрес Baud1 MOV AL, AH ;занести старший байт делителя OUT DX, AL ; MOV DX, 03FBh ;вернуться обратно к LCR MOV AL, 03 ;DLAB=0; контроля на четность нет; один стоп-бит; OUT DX, AL ;длина слова = 8 MOV DX, 03F9h ;смещение 1 для регистра IER MOV AL, 0Fh ;разрешить все типы прерываний OUT DX, AL ; MOV DX, 03FCh ;регистр MCR (смещение 4) MOV AL, 0Bh ;OUT2: RTS и DTR биты OUT DX, AL ; CLRGS: MOV DX, 03FDh ;очистить регистр LSR IN AL, DX ; MOV DX, 03F8h ;очистить регистр данных IN AL, DX ; MOV DX, 03FEh ;очистить регистр MSR IN AL, DX ; MOV DX, 03FAh ;регистр IID IN AL, DX ; IN AL, DX ;повторить для верности TEST AL, 1 ;ожидающие прерывания? JZ CLRGS ;да, повторить З_а_м_е_ч_а_н_и_е. Эта программа делает не все для установки пос- ледовательного порта. Хотя для устройства 8250 в ней полностью уста- новлены необходимые данные, следует еще проделать дополнительную рабо- ту. Так, необходимо изменить системный вектор прерываний для обеспече- ния подключения программы обработки прерываний (interrupt service routine - ISR), и кроме того, программно установить нужные характерис- тики для чипа приоритетного управления прерываниями (Priority Interrupt Controller - PIC), чтобы обеспечить реакцию на требование прерывание от каналов устройства UART. (См.:"Программирование в среде MS-DOS: Настройка MS-DOS: Аппаратный обработчик".) 4. Драйверы устройств Все версии системы MS-DOS , начиная с версии 2.0, предоставляют возможность инстоллировать драйверы устройств, разработанные пользова- телем. С точки зрения теории операционной системы, использование таких драйверов является подходящим способом для управления различными типа- ми коммуникационных интерфейсов. В следующих параграфах мы напомним и объясним исходные моменты этой главы, используя терминологию в области стандартных драйверов устройств. (См.: "Программирование в среде MS DOS: Настройка MS-DOS: Инстоллируемые драйверы устройств.") Инстоллируемый драйвер устройства содержит: 1) заголовок драйвера, который связывает этот драйвер с другими в виде списка, поддерживаемого MS-DOS; сообщает системе MS-DOS характе- ристики этого драйвера; указывает точки входа в две основные програм- мы, содержащиеся в драйвере; содержит имя драйвера (только для драйве- ров устройств по знаку); 2) данные и области сохранения, необходимые для работы драйвера; 3) две основные программы. Обычно эти программы называются "стратегическая программа" (strategy routine) и "программа прерывания" (interrupt routine). Но так как ни одна из этих программ не связана с аппаратными прерывания- ми, то для избежания путаницы с программой обработки прерываний мы бу- дем использовать название "программа обработки запросов" (Request routine) вместо Interrupt routine. MS-DOS взаимодействует с драйвером посредством выделения памяти под командный пакет размером 22 байта и передачи адреса этого пакета стратегической программе драйвера. Все данные, передаваемые между MS DOS и драйвером в обоих направлениях, проходят через этот командный пакет и программу обработки запросов. Операционная система размещает код функции и возможно счетчик байтов и адрес буфера в заданные места пакета, затем вызывает программу обработки запросов. Драйвер выполняет указанную функцию и возвращает код завершения (иногда и счетчик бай- тов) в этом пакете. 5. Два альтернативных подхода Теперь, когда принципы, используемые при создании программ управ- ления коммуникациями на основе прерываний, были обсуждены, посмотрим, как они могут быть реализованы в реальных пакетах программ. На этом пути удастся увидеть даже небольшие детали, обуславливающие недостатки и положительные решения, воплощенные в программах коммуникации. 5.1. Традиционный путь Поскольку MS-DOS не предоставляет общих функций коммуникации, фактически все общедоступные коммуникационные программы обеспечивают и устанавливают свой собственный драйверный код порта и удаляют его пе- ред возвращением управления системе MS-DOS. Такой подход влечет созда- ние коммуникационного драйвера для каждой программы и требует, чтобы этот драйвер "не инстоллировался" при выходе из программы, использо- вавшей этот драйвер. Несмотря на такие сверхтребования, большинство коммуникационных программ используют этот метод. 5.2. Альтернативный подход: создание драйвера ус- тройства коммуникации Вместо предоставления временного интерфейсного кода, который уда- ляется из системы перед возвратом на командный уровень, инстоллируемый драйвер устройства может быть построен как некоторый объект, заменяю- щий устройства COMх. И таким образом программам будут доступны все возможности. Однако этот подход не совместим с существующими терми- нальными программами, поскольку он никогда не являлся частью системы MS-DOS. 5.3. Сравнение двух методов Традиционный подход имеет ряд преимуществ. Самым очевидным явля- ется то, что такой драйвер может быть полностью спроектирован в соот- ветствии с потребностью программы. Так как этот драйвер будет исполь- зовать только одна программа, то нет необходимости рассматривать при проектировании такого драйвера общие требования, предъявляемые другими программами. Однако, если пользователь хочет сохранить возможности взаимодейс- твия доступными в фоновой резидентной программе и в некоторой другой приоритетной программе, то каждая из них при традиционном подходе дол- жна иметь свой собственный драйверный код. Так как каждый такой код включает буферные области, то дублирование этого кода для каждой прог- раммы вызывает большие накладные расходы. Единственный коммуникационный драйвер, который инстоллируется при запуске системы MS-DOS и остается активным до выключения компьютера, позволяет избежать чрезмерного расходования ресурсов системы посредст- вом предоставления как фоновой, так и приоритетной программам совмест- но разделять код драйвера. Однако маловероятно, что коммерческие сис- темы скоро предоставят возможность пользоваться такими драйверами. Кроме того, такой драйвер должен обеспечивать все (возможные для взаи- модействия) средства или должен включать управляющий интерфейс, кото- рый бы мог динамически адаптировать этот драйвер для каждой пользова- тельской программы. В настоящее время использование единственного драйвера является скорее интересным примером, хотя возможным исключением могла бы быть специальная система, в которой все программное обеспечение согласовано между собой. В такой системе общий драйвер может обеспечить значитель- ные улучшения в эффективности использования ресурсов. 6. Пакет программ управления устройствами Несмотря на ограничения, упоминаемые ранее, в первом из двух за- вершенных пакетов, представленных в этой главе, используется принцип написания отдельного драйвера устройства. Такой драйвер управляет всем аппаратно-зависимым интерфейсом и тем самым позволяет значительно уп- ростить все другие модули пакета. Этот подход представлен первым пото- му, что он довольно хорошо соответствует введенным принципам коммуни- кационных программ. Сам по себе пакет состоит из трех отдельных программ. Первая программа представляет собой собственно драйвер устройства, который становится частью MS-DOS посредством файла CONFIG.SYS. Вторая модели- рует работу модема и представляет собой в действительности программу терминала. (Функционально подобная компонента составляет сердцевину любой коммуникационной программы, независимо от того, написана ли она на ассемблере или на языке высокого уровня и несмотря на то, какая операционная система и ЭВМ используется). Третья программа - это от- дельно выполняемая программа поддержки, которая обеспечивает изменение таких характеристик как длина слова, контроль четности, уровень пере- дачи данных. В большинстве программ, использующих традиционный подход, все эти три программы объединены в одну, что делает неясным работу каждой час- ти. Здесь эти части представлены как отдельные модули. 6.1. Драйвер устройства COMDVR.ASM Этот драйвер написан таким образом, чтобы кроме устройств, ис- пользуемых по умолчанию COM1 и COM2 можно было использовать устройства с именами ASY1 и ASY2, соответствующие физически тем же аппаратным ус- тройствам, но логически являющиеся другими. Программа (COMDVR.ASM) на- писана на ассемблере (MASM) (см.рис.6-1). Хотя она представляет собой схему драйвера, ее можно использовать для разработки драйверов. 6.1.1. Текст драйвера 1: Title COMDVR Драйвер портов COM IMB -совместимых компьютеров 2: ; Jim Kyle 1987 3: ; за основу взяты идеи из многих источников...: 4: ; включая Mike Higgins, CLM Match 1985; 5: ; 6: ; COMBIOS.COM CIS Programmers'SIG; 7: ; ADVANCES MS-DOS by Ray Duncan 8: Subttl MS-DOS Определения драйвера 9: 10: Comment * Закомментированная часть макроопределения отладчика 11: Dbg Macro Ltr1,Ltr2,Ltr3 ;используется только при отладке ;драйвера 12: local Xxx 13: push es ;сохранить все используемые ре- 14: push di ;гистры 15: push ax 16: les di,cs:dbgptr ;получить указатель к экрану 17: mov ax,es:[di] 18: mov al,ltr1 ; вывод символов на экран 19: stosw 20: mov al,ltr2 21: stosw 22: mov al,ltr3 23: stosw 24: cmp di,1600 ;используются только 10 верхних 25: jb Xxx ;строк экрана 26: xor di,di 27:Xxx: mov word ptr cs:dbgptr,di 28: pop ax 29: pop di 30: pop es 31: endm 32: * ;указывает на конец закомментированной части 33: ; 34: ; Коды типов устройств 35: DevChr Equ 8000H ;символьное устройство 36: DevBlk Equ 0000H ;устройство ориентированное на диск 37: DevIoc Equ 4000H ;устроство допускающее требования IOCTL 38: DevNon Equ 2000H ;диск со структурой отличной от диска IBM 39: DevOTB Equ 2000H ; 40: DevOCR Equ 0800H ; 41: DevX32 Equ 0040H ;поддерживаются функции 42: DevSpc Equ 0010H ;допускается специальное прерывание 29H 43: DevClk Equ 0008H ;таймерное устройство 44: DevNul Equ 0004H ;фиктивное (NULL) устройство 45: DevSto Equ 0002H ;устройство стандартного вывода 46: DevSti Equ 0001H ;устройство стандартного ввода 47: ; 48: ; Биты ошибочных состояний 49: StsErr Equ 8000H ;общая ошибка 50: StsBsy Equ 0200H ;устройство занято 51: StsDne Equ 0100H ;запрос выполнен 52: ; 53: ; Значения ошибок в возрастающем порядке 54: ErrWp Equ 0 ; запись запрещена 55: ErrUu Equ 1 ; неизвестное устройство 56: ErrDnr Equ 2 ;устройство не готово 57: ErrUc Equ 3 ;неизвестная команда 58: ErrCrc Equ 4 ; 59: ErrBsl Equ 5 ; 60: ErrSl Equ 6 ;ошибка поиска 61: ErrUm Equ 7 ;неизвестная среда 62: ErrSnf Equ 8 ;сектор не найден 63: ErrPop Equ 9 ;обрыв бумаги на принтере 64: ErrWf Equ 10 ;ошибка записи 65: ErrRf Equ 11 ;ошибка чтения 66: ErrGf Equ 12 ;общая ошибка 67: ; 68: ; Структура заголовка пакета запросов на ввод/вывод 69: ; 70: Pack Struc 71: Len DB ? ;длина записи 72: Prtno DB ? ;номер устройства 73: Code DB ? ;код команды 74: Stat DW ? ;код возврата 75: Dosq DB ? ;(неиспользуемый указатель MS-DOS на список ;очереди требований) 76: Devq DB ? ;(неиспользуемый указатель на список очереди ;драйвера) 77: Media DB ? ;код среды на чтение/запись 78: Xfer DW ? ;адрес буфера пользователя: смещение отн.ад- ;реса сегмента 79: Xseg DW ? ;адрес сегмента 80: Count DW ? ;количество обрабатываемых битов 81: Sector DW ? ;значение начального сектора(только для дис- ;ка) 82: Pack Ends 83: 84: Subttl Определения драйверов устройств IBM PC 85: page 86: ; 87: ; данные для устройства 8259 88: PIC_b Equ 020h ;порт для EOI 89: PIC_e Equ 021h ;порт для разрешения прерывания 90: EOI Equ 020h ;EOI 91: ; 92: ; Смещения относительно порта устройства 2850 93: RxBuf Equ 0F8h ;базовый адрес 94: Baud1 Equ RxBuf+1 ;старший байт делителя уровня передачи ;данных 95: IntEn Equ RxBuf+1 ;регистр разрешения прерываний 96: IntId Equ RxBuf+2 ;регистр идентификации прерываний 97: Lctrl Equ RxBuf+3 ;регистр управления линией 98: Mctrl Equ RxBuf+4 ;регистр управления модемом 99: Lstat Equ RxBuf+5 ;регистр состояния линии 100: Mstat Equ RxBuf+6 ;регистр состояния модема 101: ; 102: ; Константы регистрa LCR устройства 8250 103: Dlab Equ 10000000b ;бит переключения доступа к делителю ; уровня передачи данных 104: SetBrk Equ 01000000b ;послать управляющий сигнал BREAK 105: StkPar Equ 00100000b ;установить при передаче паритет 106: EvnPar Equ 00010000b ;паритет по четности 107: GenPar Equ 00001000b ;бит генерации паритета 108: Xstop Equ 00000100b ;два стоп-бита 109: Wd8 Equ 00000011b ;длина слова=8 110: Wd7 Equ 00000010b ;длина слоа=7 111: Wd6 Equ 00000001b ;длина слова=6 112: ; 113: ; Константы регистра LSR устройства 8250 114: xsre Equ 01000000b ;регистр сдвига передатчика SR пуст 115: xhre Equ 00100000b ;регистр сохранения передатчика пуст 116: BrkRcv Equ 00010000b ;получен сигнал BREAK 117: FrmErr Equ 00001000b ;ошибка форматирования 118: PArErr Equ 00000100b ;ошибка паритета 119: OveRun Equ 00000010b ;ошибка переполнения 120: rdta Equ 00000001b ;готовность получения данных 121: AnyErr Equ BrkRcv+FrmErr+ParErr+OveRun 122: ; 123: ; Константы регистра MCR устройства 8250 124: LpBk Equ 00010000b ;включить самотестирование устройства 125: Usr2 Equ 00001000b ;управлять сигналами прерываний уст- 126: Usr1 Equ 00000100b ; ройства 127: SetRTS Equ 00000010b ;установить RTS на разъем RS232C 128: SetDTR Equ 00000001b ;установить DTR на разъем RS232C 129: ; 130: ; Константы регистра MSR устройства 8250 131: CDlvl Equ 10000000b ;уровень детектора носителя данных 132: RIlvl Equ 01000000b ; 133: DSRlvl Equ 00100000b ;уровень готовности модема 134: CTSlvl Equ 00010000b ;уровень сброса передатчика 135: CDchg Equ 00001000b ;изменился уровень детектора носителя 136: RIchg Equ 00000100b ; данных 137: DSRchg Equ 00000010b ;изменился уровень готовности модема 138: CTSchg Equ 00000001b ;изменился уровень сброса передатчика 139: ; 140: ; Константы регистра IER устройства 8250 141: S_Int Equ 00001000b ;разрешено прерывание по состоянию мо- ; дема 142: E_Int Equ 00000100b ;разрешено прерывание по состоянию ли- ; нии 143: X_Int Equ 00000010b ;разрешено прерывание по готовности ; данных к передаче 144: R_Int Equ 00000001b ;разрешено прерывание по готовности ; полученных данных 145: Allint Equ 00001111b ;разрешены все прерывания 146: ; 147: Subttl Область определений для драйвера 148: page 149: ; 150: ; Определения битов байта состояния вывода 151: ; (только для этого драйвера) 152: LinIdl Equ 0ffh ;если все биты выкючены, то сигналы ; управления не работают 153: LinXof Equ 1 ;вывод прекращается сигналом XOFF 154: LinDSR Equ 2 ;вывод прекращен до поступления DSR 155: LinSTC Equ 4 ;вывод прекращен до поступления CTS 156: ; 157: ; Определения битов байта состояния ввода 158: ; (только для этого драйвера) 159: BadInp Equ 1 ;обнаружена ошибка на линии ввода 160: LostDt Equ 2 ;буфер приема данных переполнен, поте- ; ря данных 161: OffLin Equ 4 ;устройство находится в режиме offline 162: ; 163: ; Определения битов слов специальных характеристик 164: ; (только для этого драйвера) 165: ; 166: ; 167: InEpc Equ 0001h ; 168: ; 169: ; 170: ; 171: OutDSR Equ 0001h ;DSR используется для ? 172: OutCTS Equ 0002h ;CTS используется для ? 173: OutXon Equ 0004h ;XON/XOFF используется для ? 174: OutCdf Equ 0010h ;является сигналом offline 175: OutDrf Equ 0020h ;DSR является сигналом off_line 176: 177: Unit Struc ;каждое устройство имеет некоторую ;структуру, определяющую ее состояние 178: Port Dw ? ;адрес порта ввода/вывода 179: Vect Dw ? ;смещение вектора прерывания (не номер ; прерывания) 180: Isradr Dw ? ;смещение относительно начала програм- ; мы, обслуживающей прерывание 181: OtStat Db Wd8 ;бит по умолчанию регистра LCR, уста- ; навливаемый во время INIT, 182: ;биты состояния вывода после 183: InStat Db Usr2+SetRTS+SetDTR ;бит регистра MCR, устанавливае- ; мый во время INIT 184: ;биты состояния ввода после 185: InSpec Dw InEpc ;биты специальных режимов для INPUT 186: OutSpec Dw OutXon ;биты специальных режимов для OUTPUT 187: Baud Dw 96 ;текущее значение делителя уровня пе- ; редачи(1200 bps) 188: Ifirst Dw 0 ;смещение первого символа в буфере вы- ; вода 189: Iavail Dw 0 ;смещение следующего доступного байта 190: Ibuf Dw ? ;указатель на буфер ввода 191: Ofirst Dw 0 ;смещение первого символа в буфере вы- ; вода 192: Oavail Dw 0 ;смещение следующего доступного байта ; в буфере ввода 193: Obuf Dw ? ; указатель на буфер вывода 194: Unit Ends 195: ; 196: ; 197: ; Начало кода драйвера и данных 198: ; 199: Driver Segment 200: Assume cs:driver, ds:driver, es:driver 201: org 0 ;драйвер начинается со смещением 0 от- 202: ; носительно сегмента 203: Dw Async2,-1 ;указатель на следующий драйвер 204: Dw DevChr+DevIoc ;символьное устройство с возможностью ; IOCTL 205: Dw Strtegy ;смещение к входу в стратегическую ; программу 206: Dw Request1 ;смещение к 1-ому входу обработки ; прерывания 207: Db 'ASY1' ;имя первого устройства 208: Async2: 209: Dw -1, -1 ;указатель на следующее устройство 210: Dw DevChr+DevIoc ;символьное устройство с возможностью ; IOCTL 211: Dw Strtegy ;смещение к входу в стратегическую ; программу 212: Dw Request2 ;смещение ко второму входу обработки ; прерывания 213: Db 'ASY2' ;имя второго устройства 214: 215: dbgptr Dd 0b0000000h 216: ; 217: ; Область сохранения указателя на командный пакет 218: ; 219: PackHd Dd 0 220: ; 221: ; Таблица преобразований уровня передачи данных 222:Asy_baudt Dw 50,2304 ;первое значение является искомым уров- 223: Dw 75,1536 ;нем передачи, второе - делителем 224: Dw 110,1047 ; 225: Dw 134, 857 ; 226: Dw 150, 786 ; 227: Dw 300, 384 ; 228: Dw 600, 192 ; 229: Dw 1200, 96 ; 230: Dw 1800, 64 ; 231: Dw 2000, 58 ; 232: Dw 2400, 48 ; 233: Dw 3600, 32 ; 234: Dw 4800, 24 ; 235: Dw 7200, 16 ; 236: Dw 9600, 12 ; 237: 238: ;Таблицы структур 239: ;ASY1 является умолчанием для порта COM1;прерывание INT 0ch; XON 240: ;парирет выключен; 8-битные данные, 1 стоп-бит; уровень передачи ; 1200 bps 241: Asy_tab1: 242: Unit <3f8h,30h,asy1isr,,,,,,,,in1buf,,,out1buf> 243: 244: ;ASY2 является умолчанием для порта COM2;прерывание INT 0bh; XON 245: ;парирет выключен; 8-битные данные, 1 стоп-бит; уровень передачи ;1200 bps 246: Asy_tab2: 247: Unit <2f8h,2ch,asy2isr,,,,,,,,in2buf,,,out2buf> 248: 249: Bufsiz Equ 256 ;размер буфера ввода 250: Bufmsk = Bufsiz-1 ;маска для вычисления смещений 251: In1buf Db Bufsiz DUP (?) 252: Out1buf Db Bufsiz DUP (?) 253: In2buf Db Bufsiz DUP (?) 254: Out2buf Db Bufsiz DUP (?) 255: ; 256: ; Таблица смещений ко всем функциям драйвера 257: 258: Asy_funcs: 259: Dw Init ;0 инициализация драйвера 260: Dw Mchck ;1 проверить среду (только для диска) 261: Dw BldBPB ;2 построить ВРВ (только для диска) 262: Dw Ioctlin ;3 читать IOCTL 263: Dw Read ;4 читать 264: Dw Ndread ;5 читать без разрушений 265: Dw Rxstat ;6 состояние ввода 266: Dw Inflush ;7 обновить буфер ввода 267: Dw Write ;8 писать 268: Dw Write ;9 писать с верификацией 269: Dw Txstat ;10 состояние вывода 270: Dw Txflush ;11 обновить буфер вывода 271: Dw Ioctlout ;12 писать IOCTL 272:; Следующие функции не используются в этом драйвере 273: Dw Zexit ;13 открыть (только 3.х) 274: Dw Zexit ;14 закрыть (только 3.х) 275: Dw Zexit ;15 удалить среду (только 3.х) 276: Dw Zexit ;16 выводить пока занято (только 3.х) 277: Dw Zexit ;17 278: Dw Zexit ;18 279: Dw Zexit ;19 генерировать требование IOCTL(толь- 280: Dw Zexit ;20 ко 3.2) 281: Dw Zexit ;21 282: Dw Zexit ;22 283: Dw Zexit ;23 получить логическую карту драйве- ; ра (только 3.2) 284: Dw Zexit ;24 установить логическую карту драй- ; вера (только 3.2) 285: 286: Subttl Код драйвера 287: Page 288: ; 289: ; Стратегическая программа 290: ; 291: Strtegy Proc Far 292: ; dbg 'S','R',' ' 293: mov word ptr cs:PackHd,bx ; сохранить смещение 294: mov word ptr cs:PackHd+2,es ; сохранить сегмент 295: ret 296: Strtegy endp 297:; 298: Reguest1: ;была затребована обработка запроса ; к ASY1 299: push si ;сохранить SI 300: lea si,Asy_tab1 ;установить таблицу адресов для этого ; устройства 301: jmp Short Gen_reguest 302: 303: Reguest2: ;была затребована обработка запроса ; к ASY2 304: push si ;сохранить SI 305: lea si,Asy_tab2 ;установить таблицу адресов для этого ; устройства 306: 307: Gen_reguest: 308: ; dbg 'R','R',' ' 309: pushf ;сохранить все регистры 310: cld 311: push ax 312: push bx 313: push cx 314: push dx 315: push di 316: push bp 317: push ds 318: push es 319: push cs ;установить DS=CS 320: pop ds 321: les bx,PackHd ;получить указатель на командный пакет 322: lea di,Asy_funcs ;установить DI на таблицу переходов 323: mov al,es:code[bx] ;выбрать код команды 324: cbw 325: add ax,ax ;и умножить его на размер слова 326: add di,ax 327: jmp [di] ;перейти к программе обработки,соотв. 328: ; коду команды 329:; Выход из драйвера 330: ; 331: ExitP Proc Far 332: Bsyexit: 333: mov ax,StsBsy 334: jmp Short Exit 335: 336:Mchck: 337:BldBPB: 338: Zexit: xor ax,ax 339: Exit: les bx,PackHd ;получить указатель на командный пакет 340: or ax,StsDne 341: mov es:Stat[bx],ax ;установить код возврата в командный ; пакет 342: pop es ;восстановить регистры 343: pop ds 344: pop bp 345: pop di 346: pop dx 347: pop cx 348: pop bx 349: pop ax 350: popf 351: pop si 352: ret 353:ExitE: endp 354: 355: Subttl Обслуживающие программы 356: Page 357: ; 358: ; Читать данные с устройства 359: 360: Read: 361: ; dbg 'R','d',' ' 362: mov cx,es:count[bx] ;установить счетчик количества ;требуемых для считывания данных 363: mov di,es:xfer[bx] ;получить указатель на начало поль- 364: mov dx,es:xseg[bx] ;зовательского буфера 365: push bx 366: push es 367: mov es,dx 368: test InStat[si],BadInp or LostDt 369: je No_lerr ;до сих пор ошибок нет 370: add sp,4 ;ошибка,установить коректно указатель 371: and InStat[si],Not (BadInp or LostDt) ; стека SP 372: mov ax,ErrRf 373: jmp Exit 374:No_lerr: 375: call Get_in ;получить один байт с устройства 376: or ah,ah 377: jnz Got_all ;нет ничего больше для чтения 378: stosb 379: loop No_lerr ;повторить цикл если счетчик не нуль 380: Got_all: 381: pop es 382: pop bx 383: sub di,es:xfer[bx] ;подсчитать количество реально счи- 384: mov es:count[bx],di ;танных данных и поместить их в 385: jmp Zexit ; командный пакет 386: 387: ; неразрушающее чтение с устройства 388: ; 389: Ndread: 390: mov di,ifirst[si] 391: cmp di,iavail[si] 392: jne Ndget 393: jmp Bsyexit ;буфер пуст 394: Ndget: 395: push bx 396: mov bx,ibuf[si] 397: mov al,[bx+di] 398: pop bx 399: mov es,media[bx],al ;вернуть символ 400: jmp Zexit 401: 402: ; Запрос состояния ввода 403: 404: Rxstat: 405: mov di,ifirst[si] 406: cmp di,iavail[si] 407: jne rxful 408: jmp Bsyexit ;буфер пуст 409: Rxful: 410: jmp Zexit 411: 412: ; Запрос очистки ввода 413: 414: Inflush: 415: mov ax,iavail[si] 416: mov ifirst[si],ax 417: jmp Zexit 418: 419: ; Записать данные в устройство 420: 421: Write: 422: dbg 'W','r',' ' 423: mov cx,es:count[bx] ; 424: mov di,es:xfer[bx] ; 425: mov ax,es:xseg[bx] ; 426: mov es,ax 427: Wlup: 428: mov al,es:[di] ;получить байт 429: inc di 430: Wwait: 431: call Put_out ;и вывести его в кольцевой буфер 432: cmp ah,o ;вывода, если там есть место 433: jne Wwait 434: call Start_output ;реальный вывод данных через 435: loop Wlup ; устройство 436: 437: jmp Zexit 438: 439: ;Запрос вывода состояния 440: 441: Txstat: 442: mov ax,ofirst[si] 443: dec ax 444: and ax,bufmsk 445: cmp ax,oavail[si] 446: jne Txtroom 447: jmp Bsyexit ; буфер заполнен 448: Txroom: 449: jmp Zexit ; место еще есть 450: 451: ; запрос чтения IOCTL ; вернуть строку параметров 452: 453: Ioctlin: 454: mov cx,es:count[bx] 455: mov di,es:xfer[bx] 456: mov dx,es:xseg[bx] 457: mov es,dx 458: cmp cx,10 459: je Doiocin 460: mov ax,errbsl 461: jmp Exit 462: Doiocin: 463: mov dx,port[si] ;базовый адрес порта 464: mov dl,Lctrl ;вывести состояния регистров 465: mov cx,4 ;LCR, MCR, LSR, MSR 466: Getport: 467: in al,dx 468: stos Byte Ptr[di] 469: inc dx 470: loop Getport 471: 472: mov ax,inspec[si] ;вывести биты спец.режимов для ввода 473: stos Word Ptr[di] 474: mov ax,OutSpec[si] ;вывести биты спец.режимов для вывода 475: stos Word Ptr[di] 476: mov ax,baud[si] ;вывести уровень передачи данных 477: mov bx,di 478: mov di,offset Asy_baudt+2 479: mov cx,15 480: Baudcin: 481: cmp [di],ax 482: je Yesinb 483: add di,4 484: loop Baudcin 485: Yesinb: 486: mov ax,-2[di] 487: mov di,bx 488: stos Word Ptr[di] 489: jmp Zexit 490: 491: ; Запрос очистки буфера вывода 492: 493: Txflush: 494: mov ax,oavail[si] 495: mov ofirst[si],ax 496: jmp Zexit 497: 498:; Запрос IOCTL: изменить параметры драйвера 499: 500: Ioctlout: 501: mov cx,es:count[bx] 502: mov di,es:xfer[bx] 503: mov dx,es:xseg[bx] 504: mov es,dx 505: cmp cx,10 506: je Doiocout 507: mov ax,errbsl 508: jmp Exit 509: 510: Doiocout: 511: mov dx,port[si] ;базовый адрес порта 512: mov dl,Lctrl ;регистр управления линией 513: mov al,es:[di] 514: inc di 515: or al,dlab ;установить уровень передачи 516: out dx,al 517: clc 518: jnc $+2 519: inc dx ;регистр управления модемом 520: mov al,es:[di] 521: or al,usr2 ;разрешить прерывания 522: out dx,al 523: add di,3 ;пропустить LSR, MSR 524: mov ax,es:[di] 525: add di,2 526: mov InSpec[si],ax 527: mov ax,es:[di] 528: add di,2 529: mov OutSpec[si],ax 530: mov ax,es:[di] ;установить уровень передачи данных 531: mov bx,di 532: mov di,offset Asy_baudt 533: mov cx,15 534:Baudcout: 535: cmp [di],ax 536: je Yesoutb 537: add di,4 538: loop Baudcout 539: 540: mov dl,Lctrl 541: in al,dx 542: and al,not Dlab 543: clc 544: jnc $+2 545: out dx,al ;вывести обратно 546: mov ax,errUm ;неизвестная среда 547: jmp exit 548: 549: Yesoutb: 550: mov ax,2[di] ;получить делитель 551: mov Baud[si],ax ;сохранить для дальнейшего отчета 552: mov dx,port[si] ;установить делитель 553: out dx,al 554: clc 555: jnc $+2 556: inc dx 557: mov al,ah 558: out dx,al 559: clc 560: jnc $+2 561: mov dl,Lctrl ;регистр управления линией 562: in al,dx ;получить с LCR данные 563: and al,not Dlab 564: clc 565: jnc $+2 566: out dx,al ;вывести обратно 567: jmp Zexit 568: 569: Subttl Программы работы с кольцевыми буферами 570: Page 571: 572: Put_out Proc Near ;Затолкнуть содержимое AL в кольцевой буфер 573: push cx ; вывода 574: push di 575: pushf 576: cli 577: mov cx,oavail[si] ;выбрать указатель на место в буфере 578: mov di,cx ;вывода,куда может быть записан выво- 579: inc cx ;димый байт данных 580: and cx,bufmsk ;этот указатель совпал с указателем 581: cmp cx,ofirst[si] ; на очередной байт для выборки 582: je poerr ;да (переполнение) - ничего не делать 583: add di,obuf[si] ;нет 584: mov [di],al ;вытолкнуть байт данных из буфера 585: mov oavail[si],cx 586: dbg 'p','o',' ' 587: mov ah,0 588: jmp Short Poret 589: Poerr: 590: mov ah,-1 591: Poret: 592: popf 593: pop di 594: pop cx 595: ret 596: Put_out:Endp 597: 598: Get_out Proc Near ;Получить очередной символ из кольцевого буфе- 599: push cx ; ра вывода 600: push di 601: pushf 602: cli 603: mov di,ofirst[si] ;выбрать указатель на очередной выби- 604: cmp di,oavail[si] ;раемый символ; он совпадает с указа- 605: jne Ngoerr ; телем на свободное место в буфере 606: mov ah,-1 ; Да - буфер пуст 607: jmp Short Goret 608: Ngoerr: 609: dbg 'p','o',' ' 610: mov cx,di 611: add di,obuf[si] ;нет 612: mov al,[di] ;выбрать символ в AL 613: mov ah,0 614: inc cx 615: and cx,bufmsk ;продвинуть указатель на очередной 616: mov ofirst[si],cx ; доступный для выборки символ 617: Goret: 618: popf 619: pop di 620: pop cx 621: ret 622: Get_out Endp 623: 624: Put_in Proc Near ;Записать символ из AL в кольцевой буфер ввода 625: push cx 626: push di 627: pushf 628: cli 629: mov di,iavail[si] 630: mov cx,di 631: inc cx 632: and cx,bufmsk 633: cmp cx,ifirst[si] 634: jne Npierr 635: mov ah,-1 636: jmp Short Piret 637: Npierr: 638: add di,ibuf[si] 639: mov [di],al 640: mov iavail[si],cx 641: dbg 'p','i',' ' 642: mov ah,0 643: Piret: 644: popf 645: pop di 646: pop cx 647: ret 648: Put_in Endp 649: 650: Get_in Proc Near ;Выбрать очередной символ из кольцевого 651: push cx ; буфера в AL 652: push di 653: pushf 654: cli 655: mov di,ifirst[si] 656: cmp di,iavail[si] 657: je Gierr 658: mov cx,di 659: add di,ibuf[si] 660: mov al,[di] 661: mov ah,0 662: dbg 'g','i',' ' 663: inc cx 664: and cx,bufmsk 665: mov ifirst[si],cx 666: jmp Short Giret 667: Gierr: 668: mov ah,-1 669: Giret: 670: popf 671: pop di 672: pop cx 673: ret 674: Get_in: Endp 675: 676:Subttl Программа диспетчеризации прерываний 677: Page 678: 679: Asy1isr: 680: sti 681: push si 682: lea si,asy_tab1 683: jmp short Int_serve 684: 685: Asy2isr: 686: sti 687: push si 688: lea si,asy_tab2 689: 690: Int_serve: 691: push ax 692: push bx 693: push cx 694: push dx 695: push di 696: push ds 697: push cs ;DS = CS 698: pop ds 699:Int_exit: 700: dbg 'I','x',' ' 701: mov dx,port[si] ;базовый адрес порта 702: mov dl,IntId ;регистр идентификации прерываний 703: in al,dx 704: cmp al,00h 705: je Int_modem ;прерывание по изменению состояния 706: jmp Int_mo_no ; модема 707: Int_modem: 708: dbg 'M','S',' ' 709: mov dl,mstat 110: in al,dx ;чтение содержимого регистра MSR 711: test al,cdlvl ;присутствует 712: jnz Msdsr ;да, проверить сигнал DSR 713: test OutSpec[si],OutCdf ;нет,сигнал CD является offline 714: jz Msdsr 715: or InStat[si],offlin 716: Msdsr: 717: test al,DSRlvl ;сигнал DSR присутствует 718: jnz Dsron ;да, разобраться с ним 719: test OutSpec[si],OutDSR ;нет 720: jz Dsroff 721: or OtStat[si],LinDSR ;да 722: Dsroff: 723: test OutSpec[si],OutDrf ; 724: jz Mscts 725: or InStat[si],offlin ;да, установить флаг 726: jmp Short Mscts 727: Dsron: 728: test OtStat[si],LinDSR; 729: jz Mscts 730: Xor OtStat[si],LinDSR ;да,очистить его 731: call Start_output 732: Mscts: 733: test al,CTSlvl ;сброс передатчика? 734: jnz Ctson ;да, разобраться с ним 735: test OutSpec[si],OutCTS ;нет 736: jz Int_exit2 737: or OtStat[si],LinСTS ;да, прекратить работу 738: jmp Short Int_exit2 739: Ctson: 740: test OtStat[si],LinCTS; 741: jz Int_exit2 742: Xor OtStat[si],LinCTS ;да 743: jmp Short Int_exit1 744: Int_mo_no: 745: cmp al,02h 746: jne Int_tx_no 747: Int_txmin: 748: dbg 'T','X',' ' 749: Int_exit1: 750: call Start_output ;попытка послать еще раз 751:Int_exit2: 752: jmp Int_exit1 753: Int_tx_no: 754: cmp al,04h 755: jne Int_tx_no 756: Int_receive: 757: dbg 'R','X',' ' 758: mov dx,port[si] 759: in al,dx ;взять символ с устройства 8250 760: test OutSpec[si],OutXon ;допустимы сигналы XON/XOFF? 761: jz Stuff_in ;нет 762: cmp al,'S' and 01Fh ;да,это XOFF? 763: jne Isq ;нет, проверить на XON 764: or OtStat[si],LinXof ;да, запретить вывод 765: jmp Int_exit2 ;сам сигнал в буфере не сохранять 766: Isg: 767: cmp al,'Q' and 01Fh ;это XON? 768: jne Stuff_in ;нет, сохранить символ в буфере 769: test OtStat[si],LinXof ;да,состояние ожидания разреше- ; ния вывода 770: jz Int_exit2 ;нет, игнорировать сигнал XON 771: xor OtStat[si],LinXof ;да,разрешить вывод (выключить ; бит XOFF) 772: jmp Int_exit1 ;и попытаться продолжить вывод 773: Int_rec_no: 774: cmp al,06h 775: jne Int_done 776: Int_rxstat: 777: dbg 'E','R',' ' 778: mov dl,Lstat 779: in al,dx 780: test InSpec[si],InEpc ;возвратить их как коды? 781: jz Nocode ;нет,только установить сигнал ошибки 782: and al,AnyErr ;да,вывести соответствующий бит ошибки 783: or al,080h 784: Stuff_in: 785: call put_in ;втолкнуть символ ввода в буфер 786: cmp ah,0 ;действия прошло успешно? 787: je Int_exit3 ;да 788: or InStat[si],LostDt ;нет,установить бит потери дан- 789: Int_exit3: ; ных 790: jmp Int_exit 791: Nocode: 792: or InStat[si],BAdInp 793: jmp Int_exit3 794: Int_done: 795: clc 796: jnc $+2 797: mov al,EOI ;все сделано 798: out PIC_b,al 799: pop ds ;восстановление регистров 800: pop di 801: pop dx 802: pop cx 803: pop bx 804: pop ax 805: pop si 806: iret 807: 808: Start_output Proc Near 809: test OtStat[si],LinIdl ;заблокировано? 810: jnz Dont_start ;да,вывод не разрешен 811: mov dx,port[si] ;нет,проверить устройство UART 812: mov dl,Lstat 813: in al,dx 814: test al,xhre ;Регистр HR пуст? 815: jz Dont_start ;нет_ предыдущий вывод не заполнен 816: call get_out ;да - есть ли в буфере вывода 817: or ah,ah ; что-нибудь? 818: jnz Dont_start ;в буфере вывода ничего нет 819: mov dl,RxBuf ;вывести очередной символ из буфера 820: out dx,al ; вывода 821: dbg 's','o',' ' 822: Dont_start: 823: ret 824: Start_output Endp 825: 826:Subttl Программа инициализации драйвера 827: Page 828: 829: Init: lea di,$ ;освобождаемая часть 830: mov es:xfer[bx],di 831: mov es:xseg[bx],cs 832: 833: mov dx,port[si] ;базовый адрес порта 834: mov dl,Lctrl 835: mov al,dlab 836: out dx,al 837: clc 838: jnc $+2 839: mov dl,RxBuf 840: mov ax,baud[si] ;установить уровень передачи 841: out dx,al 842: clc 843: jnc $+2 844: inc dx 845: mov al,ah 846: out dx,al 847: clc 848: jnc $+2 849: 850: mov dl,Lctrl ;установить 851: mov al,OtStat[si] ;из таблицы 852: out dx,al 853: mov OtStat[si],0 ;очистить состояние 854: clc 855: jnc $+2 856: mov dl,IntEn ;регистр IER 857: mov al,AllInt ;установить уровень передачи 858: out dx,al 859: clc 860: jnc $+2 861: mov dl, Mctrl ;установить MCR 862: mov al,InStat[si] ;из таблицы 863: out dx,al 864: mov InStat[si],0 ;очистить состояние 865: 866: ClRgs: mov dl,Lstat ;очистить LSR 867: in al,dx 868: mov dl,Rxbuf ;очистить RX 869: in al,dx 870: mov dl,Mstat ;очистить MSR 871: in al,dx 872: mov dl,IntId ;очистить IID 873: in al,dx 874: in al,dx 875: test al,1 ;незавершенные прерывания 876: jz ClRgs ;да, повторить цикл 877: 878: cli 879: xor ax,ax ;Установить вектор прерывания 880: mov es,ax 881: mov di,Vect[si] 882: mov ax,IsrAdr[si] ;из таблицы 883: stosw 884: mov es:[di],cs 885: 886: in al,PIC_e 887: and al,0E7h 888: clc 889: jnc $+2 890: out PIC_e,al 891: sti 892: 893: mov al,EOI 894: out PIC_b,al 895: 896: dbg 'D','I',' ' ;драйвер установлен 897: jmp Zexit 898: 899: Driver Ends 900: End Рис.6-1. COMDRV.ASM Первая часть текста драйвера (после всех необходимых для MASM операторов в строках 1 и 8) является макроопределением (строки 10-32). Эта макрокоманда нужна только во время отладки. Она не использует изощренных возможностей аппаратуры и не требует отладочных программ, более сложных, чем отладчик DEBUG.COM (отладочная технология обсужда- ется после представления программы драйвера). 6.1.2. Определения Реальный исходный текст программы драйвера состоит из трех частей определений EQU (строки 34 - 194), за ними следует код модулятора и область данных (строки 197 - 900). Первая часть определений (строки 34 - 82) задает символьные имена для допустимых значений управляющих би- тов драйверного устройства MS-DOS и структуры драйверного устройства. Вторая часть определений (строки 84 - 145), устанавливает соот- ветствие между именами и портами, а также значения битов, которые ас- социируются с аппаратурой IBM - 8259 PIC и 8250 UART. Третья часть оп- ределений (строки 147 - 194) назначает именам управляющие значения и структуры, ассоциируемые с этим драйвером. Используемый здесь метод определений рекомендуется для всех драй- веров. При переносе этого драйвера из архитектуры IBM в некоторую дру- гую аппаратную среду, необходимо переназначить адреса портов и значе- ния битов в строках 84 - 145. Управляющие величины и структуры для этого специфического драйве- ра (определения которых находятся в третьей части определений) обеспе- чивают средства, с помощью которых отдельная поддерживающая программа может модифицировать действия любого из двух логических драйверов. Они также позволяют драйверу возвращать, если есть необходимость, информа- цию о состоянии в программу поддержки или в использующуюся программу. В программе реализована только часть средств, но для расширений заре- зервировано достаточно места. Достаточно добавить некоторое количество определений и одну или две дополнительные процедуры для получения до- полнительных возможностей драйвера, таких как автоматическое расшире- ние таблицы символов, преобразования регистров клавиатуры и тому по- добное, если это необходимо. 6.1.3. Заголовки и таблицы структур Сам драйверный код начинается связанной между собой парой блоков заголовков драйверного устройства, один для ASY1 (строки 201 - 207) и второй - для ASY2 (строки 208 - 213). Следом за заголовками, в строках 215 - 236, находятся область, зарезервированная для отладчика (строка 215), указатель к командному пакету (строка 219) и таблица преобразо- ваний уровней передачи в бодах (строки 221 - 236). За таблицей преобразований следуют таблицы структур, которые со- держат все данные, уникальные для устройства ASY1 (строки 239 - 242) и ASY2 (строки 244 - 247). За таблицами структур резервируются буферные области (строки 249 - 254). Для каждого порта резервируются одна об- ласть для ввода и одна для вывода. Все эти буферы имеют одинаковые размеры; для простоты размер буфера задается именем (строка 249), так что он может быть легко изменен с помощью редактирования в программе только одной этой строки. В этом случае размер является произвольным, но если предполагает- ся передача файлов, то буфер должен иметь возможность вместить по крайней мере количество данных, поступающих за 2 сек (240 байт при скорости 120 bps), чтобы избежать потери данных во время записи на диск. В любом случае выбранный размер буфера должен быть степенью 2, а если имеется в виду видеодисплей, то размер должен быть не меньше 8 байтов, чтобы предохранить потерю символов при скроллинге экрана. Если есть потребность в дополнительных портах, то дополнительные заголовки могут быть добавлены после строки 213; в этом случае также необходимы соответствующие таблицы структур для каждого драйвера, плюс соответствующие пары буферов. В конце рассматриваемой области размещается таблица ссылок (dispatch table), которая содержит список смещений ко всем необходимым подпрограммам драйвера. Их использование обсуждается ниже. 6.1.4. Стратегическая программа и программа обра- ботки запросов С учетом всех данных, код программы начинается со стратегической программы (строки 289 - 296), которая используется обоими портами . Эта программа сохраняет адрес командного пакета, получаемого от MS-DOS, для дальнейшего использования программой обработки запросов. После этого программа возвращает управление MS-DOS. Оба порта также разделяют программы обработки запросов (строки 298 - 567), но эти два драйвера распознаются посредством адреса, раз- мещаемого в регистре SI. Этот адрес указывает на ту таблицу структуры, которая уникальна для каждого порта и содержит базовый адрес порта, вектор прерываний, связанный с этим портом, смещение к программе обра- ботки прерывания относительно драйверного сегмента, базовые смещения к буферам ввода и вывода для этого порта, два указателя для каждого из этих буферов, установленные состояния для ввода и вывода (включая уро- вень передачи) этого порта. Единственное различие между драйверами портов состоит только в указателе, который размещается в регистре SI. Сама же программа обработки запросов одинакова для обоих портов и раз- деляется ими. Для каждого устройства в ней есть своя точка входа (строка 298 для ASY1 и строка 303 для ASY2). Здесь сохраняется значе- ние регистра SI, после чего в него загружается адрес таблицы структур соответствющего устройства. Эти две программы объединяются, начиная со строки 307 (метка Gen_request). В этом общем для обеих программ коде сохраняются все используемые регистры (строки 309 - 318), в регистр DS загружается значения регист- ра CS (строки 319 - 320), восстанавливается указатель на командный па- кет, сохраненный стратегической программой (строка 321), используется указатель для получения кода функции (строка 323), этот код использу- ется для вычисления смещения в таблице адресов (строки 324 -326) и вы- полнения индексного перехода (строки 322 - 327) к программе, вход в которую определяется из таблицы ссылок (строки 256 - 284). В таблице ссылок помещаются указатели на входы в программы, которые выполняют соответствующие действия по указанной в запросе функции (строки 336. 360, 3389, 404, 421, 441, 453, 829). Хотя для драйверного устройства в версии 3.2 MS-DOS список допус- тимых функций находится в диапазоне 0 - 24, не все эти функции исполь- зуются. В ранних версиях MS-DOS были разрешены функции 0 - 12 (версии 2.х) и функции 0 - 16 (версии 3.0 и 3.1). В данном драйвере разрешены все 24 функции. Те из них, которые ничего не должны делать, возвращают вызывающей программе код DONE и NO ERROR. Поскольку вызов программы обработки запросов происходит непосредственно из MS-DOS , то нет необ- ходимости производить контроль на неуспешный код возврата. В действи- тельности, так как биты атрибутов заголовка никогда не принимают зна- чения 13 - 24, то 24 байта, которые выделяются для них в таблице (строки 273 - 284), могут быть сэкономлены. Однако они включены в таб- лицу с единственной целью показать, как могут быть согласованы несу- ществующие команды. Непосредственно за выполняемой командой индексного перехода в строках 239 - 253 (в процедуре ExitP) находится используемый всеми программами обработки запросов программный код, служащий для занесения информации состояния в командный пакет, восстановления общих регистров и возврата в вызывающую программу. Альтернативные точки входа для сос- тояний BUSY ( строка 323), NO ERROR (строка 338), или ошибочных кодов (точка входа Exit с кодом ошибки в регистре AX - строка 339) не только сокращают программный код, но также улучшают читабельность программы. Все программы обработки запросов, за исключением программы Init (строки, начиная с 829), находятся непосредственно за ядром (строки 358 - 568). Каждая из этих программ выполняет только одну свойственную ей работу.( Например, читать данные, писать данные.) Программа "Чте- ние"( Read, строки 360 - 385) является типичной для подобных программ. Вначале эта программа выбирает из командного пакета количество затре- бованных для чтения байтов и адрес пользовательского буфера. Затем, указатель на командный пакет сохраняется в стеке с помощью команды PUSH с тем, чтобы использовать регистры ES и BX для ссылок к буферу ввода порта. Перед обращением к подпрограмме Get_in осуществляется доступ к этому буферу вввода, происходит проверка байта состояния ввода (строка 368). Если обнаруживается некоторая ошибка, то в строках 370 - 373 очищается флаг состояния; восстанавливается указатель из стека и осу- ществляется переход на выход из драйвера в точке обнаружения ошибки. Если никакие ошибки не обнаружены, то в строке 375 происходит вызов подпрограммы Get_in, которая осуществляет доступ к буферу ввода; в стоках 376 - 377 осуществляется проверка на считывание данного коли- чества байтов. Порции считанных из буфера ввода байтов сохраняются в пользовательском буфере (строка 378). Это происходит до тех пор, пока не будут получены все затребованные для ввода байты или пока больше не будет доступных для ввода байтов. В любой ситуации управление переходит к подпрограмме Got_all, где в строках 381 - 382 восстанавливается сохраненный в стеке указатель на командный пакет. В строках 383 и 384 производятся действия по приведе- нию в соответствие количества затребованных для чтения байтов с тем реальным их количеством, который был получен. Наконец в строке 385 происходит переход на подпрограмму выхода из драйвера в точке Zexit (нет ошибок). 6.1.5. Буферизация Оба буфера для каждого драйвера имеют тип, известный под названи- ем "циклический" или "круговой" буфер. Действительно, такой буфер бес- конечен. Доступ к такому буферу осуществляется посредством указателей. Как только указатель в процессе увеличения превысит значение адреса конца буфера, ему присваивается значение адреса начала буфера. Для циклических буферов используются два указателя: один для операции за- писи в буфер, другой - для операции чтения из буфера. Указатель чтения всегда указывает в буфере на следующий байт для чтения, указатель за- писи - на место, куда будет записываться следующий байт. Когда эти два указателя имеют одно и тоже значение - то буфер считается пустым: следующий байт, который должен будет быть прочитан, еще не записан. Условие, определяющее заполненность буфера, является более сложным для тестирования: указатель записи при каждом увеличении сравнивается с указателем чтения; как только они становятся равными, то попытка записи и, соответственно, увеличения указателя записи, при- ведет к ложному состоянию "буфер пустой", - поэтому такое состояние определяет заполненность буфера. Все действия с буфером осуществляются посредством четырех проце- дур (строки 569 - 674). Процедура Put_out (строки 572 - 596) записыва- ет текущий байт в выводной буфер драйвера и возвращает состояние "бу- фер заполнен" посредством занесения значения 0FFH в регистр AH. Проце- дура Get_out(строки 598 - 622) читает очередной байт из выводного бу- фера драйвера и возвращает в регистре AH значение 0FFH в случае, когда нет доступных для чтения байтов. Процедуры Put_in (строки 624 - 548) и Get_in строки 650 - 674) делают в точности то же самое, что и процеду- ры Put_out и Get_out, но только с вводным буфером драйвера. Эти проце- деры используются как подпрограммами обработки запросов, так и обслу- живающей программой обработки аппаратного прерывания (ISR - Interrupt Service Routine). 6.1.6. Программы обработки прерываний Наиболее сложной частью данного драйвера является программа обра- ботки прерываний - ISR ( строки 676 - 806), которая собственно и опре- деляет, какие из четырех возможных действий необходимо выполнить для данного порта. Подобно подпрограммам обработки запросов ISR обеспечи- вает уникальные точки входов для каждого из портов (строка 679 для ASY1 и строка 685 для ASY2). В каждой точке входа первоначально сохра- няется значение регистра SI, и затем в SI загружается адрес таблицы структуры порта. Начиная со строки 690 расположена общая часть ISR. В этой части сначала сохраняются все регистры (строки 690 - 698), а за- тем определяется одна из четырех возможных обрабатывающих функций, не- обходимая для выполнения требуемого действия. Самой сложной частью программы ISR является декодирование условий состояний модема. Так как результирующая информация не используется в этом драйвере (хотя она может быть использована для предотвращения по- пыток передачи данных, когда устройство находится в состоянии off line), эти возможности программы ISR могут быть удалены так, чтобы об- рабатывались только прерывания по передаче и получению данных. Для то- го, чтобы сделать это, Allint (строка 145) должна быть изменена так, чтобы были включены только биты передачи и получения (03H или, что то же самое, 00000011B). Части передачи и получения программы ISR включают в свой состав по умолчанию управление потоком X0N/XOFF. Это управление, выполняемое скорее на уровне ISR, чем в использующейся программе, минимизирует время, требуемое для ответа на поступающий сигнал XOFF. Разрешение или запрещение управления потоком осуществляется путем установления слова OutSpec в таблице структур утилитой Driver Status (описывается ниже) посредством функции IOCTL (прерывание 21H, функция 44H). Когда управление потоком разрешено, при поступление любого сим- вола XOFF (11H) приостанавливаются все действия по передаче данных до тех пор, пока не поступит символ XON (13H). Символы XOFF или XON про- ходят через этот драйвер. Когда управление потоком запрещено, драйвер пропускает все символы в обоих направлениях. Для двоичного файла уп- равление потоком должно быть запрещено. Действие по передаче данных достаточно просто,- программа вызыва- ет процедуру Start_output(строка 750). Процедура Start_output детально описывается ниже. Действие по получению даных такое же, как и по передаче, за иск- лючением тестирования управления потоком. Первоначально программа ISR выбирает поступивший байт с устройства UART (строки 758 - 759) с тем, чтобы избежать возможности ошибочного переполнения. Затем ISR проверя- ет спецификатор ввода (строка 760), чтобы определить, разрешено или нет управление потоком. Если управления потоком не разрешено, то прог- рамма передает управление на строку 784, в которой происходит обраще- ние к процедуре Put_in для занесения поступившего байта в буфер ввода. Если управление потоком разрешено, то поступивший байт сравнива- ется с символом XOFF (строка 762 - 765). При совпадении выполняются действия по запрещению операции вывода, а поступивший байт игнорирует- ся. При несовпадении, поступивший байт сравнивается с символом XON (строка 766 - 768). В этом случае, при несовпадении, происходит пере- ход к строке 784, а при совпадениии - разрешается выполнять операции вывода (если до этого они были запрещены). В любом случае, если посту- пивший байт совпадает с символом XON, он игнорируется. Когда в процессе выполнения программы ISR управление доходит до точки Stuff_in(строка 784), то здесь происходит обращение к процедуре Put_in , которая записывает поступивший байт в буфер ввода. Если в бу- фере ввода нет больше места для только что поступившего байта, во флажках состояния ввода взводится флаг "потеря битов данных" (lost-databit) (строка 788). В противном случае программа получения данных завершает свою работу. В случае, если прерывание определяется состоянием линии, будет считываться информация из регистра LSR (строки 776 - 779). Если специ- фикация ввода содержит соответствующие указания, то содержимое LSR преобразуется в расширенный графический символ IBM PC путем установки седьмого бита в 1, и этот символ заносится в буфер ввода как если бы он был символом данных. В противном случае прерывание по LSR просто приведет к установке бита BadInp во флагах состояния, которые могут быть затем считаны с помощью функции IOCTL Read. Программа ISR после завершения всех необходимых для обработки прерывания действий восстанавливает состояние, которое было в момент возникновения прерывания, и возвращает управление прерванной программе (строка 794 - 806). 6.1.7. Программа Start_output Start_output (строка 808 - 824) подобно четырем процедурам работы с буферами используется и программами обработки запросов, и программой ISR. Целью этой программы является: начальное инициирование такой пе- редачи байта, при которой вывод не блокируется управлением потоком; обнуление регистра сохранения Transmit Holding Register устройства UART и, если в циклическом буфере вывода находится байт, передача бай- та. Эта программа использует буферную программу Get_out для осуществ- ления доступа к циклическому буферу вывода и проверки в нем доступного для вывода байта. Если все эти условия выполняются, то соответствующий байт посылается в регистр сохранения устройства UART (строки 819 - 820). 6.1.8. Программа начального инициирования Программа начального инициирования (строка 829 - 897) является самой критичной для дальнейшего успешного выполнения операций данным драйвером. Эта программа расположена в самом конце драйвера. Это сде- лано для того, чтобы место, которое она занимает в памяти, можно было бы освободить после выполнения всех необходимых действий по начальной установке драйвера. По существу эта программа очищает каждый регистр устройства 8250 посредством чтения их содержимого перед разрешением прерываний. Эти действия осуществляются в цикле до тех пор, пока устр- йство 8250 наконец не укажет, что к нему нет запросов, находящихся в ожидании. Странная последовательность команд Clc и jnc$+2, которая повторяется в этой программе, необходима для установки требуемой за- держки времени для высокоскоростных машин (6 МГц и выше). Это нужно для того, чтобы устройство 8250 имело бы время для стабилизации перед другой попыткой доступа. Эта задержка не ухудшает реакцию для медлен- ных машин. 6.1.9. Использование COMDVR На первым шаге использования этого драйвера необходимо выполнить ассемблирование исходного текста программы с помощью транслятора Microsoft Macro Assembler (MASM). Затем следует с помощью программы Microsoft Object Linker (LINK) создать EXE-файл. Следующим шагом надо использовать программу EXE2BIN для преобразования EXE-файла в двоичный файл. Наконец, необходимо включить в файл CONFIG.SYS строку DEVICE=COMDVR.SYS. В этом случае при загрузке системы будет происхо- дить установка драйвера COMDVR. На рисунке 6-2 приведены все необходимые действия в предположе- нии, что для создания и модификации файла CONFIG.SYS используется ре- дактор EDLIN, и что все команды вызываются из корневого каталога заг- рузочного устройства. Поскольку устройства, устанавливаемые COMDVR, не используют стан- дартные имена, принятые в MS-DOS, не будет никаких конфликтов с любыми другими программами, использующими традиционные ссылки к портам. Такие программы не будут использовать наш драйвер, и в случае, если эти программы правильно работают и восстанавливают вектора прерываний пе- ред возвратом управления MS-DOS, не будут создавать каких-либо проб- лем. Создание драйвера C:> MASM COMDVR C:> LINK COMDVR C:> EXE2BIN COMDVR.EXE COMDVR.SYS Модификация CONFIG.SYS (^Z = Ctrl+Z) C:> EDLIN CONFIG.SYS *#1 * DEVICE= COMDVR.SYS *^Z *E Рис.6-2. Ассемблирование, редактирование и инстолляция COMDVR 6.1.10. Техника отладки драйвера Отладка драйвера, подобно отладке любой части самой системы MS-DOS, довольно трудная задача в сравнении с обычной проверкой прог- раммы. Это обусловлено тем, что отладочная программа DEBUG.COM ( или DEBUG.EXE) сама использует функции MS-DOS для вывода результатов своей работы на дисплей.Когда проверяются эти функции, то их использование с помощью DEBUG приводит к разрушению проверяемых данных. Поэтому MS-DOS всегда сохраняет свои собственные адреса возврата в одном и том же месте. Любой вызов системной функции внутри MS-DOS обычно вызывает прекращение работы системы. Для возобновления работы необходимо в этом случае выключить систему и повторно ее запустить. Одним из способов преодоления этих трудностей является приобрете- ние дорогих отладочных средств. Однако, есть один легкий способ обхода этой проблемы: вместо использования функций MS-DOS для трассировки ра- боты программы нужно писать данные непосредственно в оперативную па- мять видеоадаптера, как это делается с помощью макрокоманды DBG (стро- ки 10 - 34 драйвера СOMDVR.ASM). В каждой точке программы, где желательно получить отчет, вызыва- ется эта макрокоманда с трехсимвольной строкой параметра. При вызове задается уникальная строка так, чтобы на экране можно было идентифици- ровать выполняемую последовательность действий. В момент вызова макро- команды DBG расширяется в машинный код, который сохраняет все регистры и затем заносит трехсимвольную строку в оперативную память видеоадап- тера. Используются только верхние 10 строк экрана (800 символов или 1600 байтов памяти экрана). Макрокоманда использует данный указатель для доступа к памяти экрана и рассматривает эту память как циклический буфер. При использовании монохромного адаптера указатель Dbgptr (строка 215) устанавливается на адрес B000:0000H, при использовании адаптеров CGA или EGA ( в режиме CGA) этот указатель устанавливается на адрес B800:0000H. В наиболее часто используемых программах обработки запросов, та- ких как Read и Write, обращение к DBG происходит в начальных строках (например, строки 361 и 422). В исходном тексте все эти вызовы заком- ментированы. Поэтому, чтобы провести отладку, необходимо откорректиро- вать программу так, чтобы вызов этих макрокоманд был разрешен. При активизированной макрокоманде DBG в десяти верхних строках экрана выводятся последовательности отчетов, таких как RR Tx, которые непосредственно заносятся в оперативную память видеоадаптера. Так как при этом не используются никакие функции MS-DOS, то не возникает ника- ких противоречий с драйвером. Хотя при таком подходе и нарушается нормальное использование сис- темы в процессе отладки, зато существенно упрощается трассирование критических (по времени) ситуаций, таких как обработка аппаратных пре- рываний. Кроме того, все включения DBG происходят в места проверки ус- ловий, которые выполняются только тогда, когда драйвер работает пра- вильно. Например, если на дисплее не появилось сообщение pi, то не обра- ботано аппаратное прерывание по получению данных, а отсутствие до пос- ле ix говорит о том, что данные не посланы правильно. Конечно, сразу после завершения отладки следует удалить или за- комментировать обращения к DBG. Обычно их удаляют. Мы же их оставили для демонстрации техники использования и, самое главное, точек включе- ния в текст программы, обеспечивающих максимум информации и минимум вывода на экран. 6.2. Простая программная модель модема Во второй части настоящего пакета приведена программная модель модема (ENGINE.ASM, на рис.6-3). Основной цикл этой программы состоит только из двенадцати командных строк (строки 9 - 20). Первые пять (строки 9 - 13) осуществляют начальный контакт программы с последова- тельным портом, а последние две - возвращение к командному уровню. 1: TITLE engine 2: 3: CODE SEGMENT PUBLIC 'CODE' 4: 5: ASSUME CS:CODE,DS:CODE,ES:CODE,SS:CODE 6: 7: ORG 0100h 8: 9: START: mov dx,offset devnm ;открыть поименованное устройство 10: mov ax,3d02h ; (ASY1) 11: int 21h 12: mov handle,ax ;сохранить дескриптор устройства 13: jc quit 14: alltim: call getmdm ;основной цикл модемного устройства 15: call putcrt 16: call getkbd 17: call putmdm 18: jmp alltim 19: quit: mov ah,4ch ;точка выхода из программы 20: int 21h 21: 22: getmdm proc ;получение данных с входа модема 23: mov cx,256 24: mov bx,handle 25: mov dx,offset mbufr 26: mov ax,3F00h 27: int 21h 28: jc guit 29: mov mdlen,ax 30: ret 31:getmdm endp 32: 33: getkbd proc ;чтение данных с клавиатуры 34: mov kblen,0 ; 35: mov ah,11 ;клавиша нажата 36: int 21h 37: inc al 38: jnz nogk ;нет 39: mov ah,7 ;да - считать ее 40: int 21h 41: cmp al,3 ;это была клавиша "выход" 42: je quit ;да 43: mov kbufr,al ;нет сохранить ее значение 44: inc kblen 45: cmp al,13 ;это была клавиша "Enter"? 46: jne nogk ;нет 47: mov byte ptr kbufr+1,10 ;да-добавить LF(новая строка) 48: inc kblen 49: nogk: ret 50: getkbd endp 51: 52: putmdm proc ;вывод данных в модем 53: mov cx,kblen 54: jcxz nopm 55: mov bx,handle 56: mov dx,offset kbufr 57: mov ax,4000h 58: int 21h 59: jc quit 60: nopm: ret 61: putmdm endp 62: 63: putcrt proc 64: mov cx,mdlen 65: jcxz nopc 66: mov bx,1 67: mov dx,offset mbufr 68: mov ah,40h 69: int 21h 70: jc quit 71: nopc: ret 72: putcrt endp 73: 74: devnm db 'ASY1',0 75: handle dw 0 76: kblen dw 0 77: mdlen dw 0 78: mbufr db 256 dup (0) 79: kbufr db 80 dup (0) 80: 81: CODE ENDS 82: END START Рис.6-3. ENGINE.ASM Таким образом, по существу только средние пять командных строк (строки 14 - 18) выполняют основной объем работы программы. Четыре ко- мандные строки осуществляют вызов подпрограмм, которые получают/зано- сят данные с/на консоль и последовательный порт. Закрывает цикл коман- да JMP, передающая управление на начало цикла. Такая структура подчер- кивает тот факт, что в основе модемного устройства лежит просто цикл передачи данных. Поскольку таймирование и преобразование данных управляется драй- вером, то четыре эти подпрограммы являются по существу буферизованным интерфейсом с программами MS-DOS "Чтение из файла или устройства" и "Запись в файл или устройство". Например, подпрограмма getmdm (строки 22 - 31) запрашивает у MS-DOS чтение максимум 256 байтов из последовательного устройства и затем сохраняет реальное количество прочитанных байтов в слове mdlen. Драйвер возвращает управление немедленно без ожидания данных, так что обычное количество возвращаемых данных будет либо 0 либо 1. Если скроллинг экрана вызывает задержку цикла, это количество может быть больше, но оно никогда не превышаеи двенадцати символов. Когда вызывается процедура putcrt (строка 63 - 72), то она прове- ряет значение mdlen.Если это значение равно нулю, то процедура ничего не делает, в противном случае она запрашивет у MS-DOS выполнение запи- си этого количества байтов из mbufr (куда процедура getmdm занесла данные) на дисплей, после чего происходит выход из процедуры. Подобным образом процедура getkbd считывает ключевую строку, вве- денную с клавиатуры, сохраняет ее в kbufr, фиксирует ее длину в kblen. Процедура putmdm проверяет значение kblen и, если это значение не рав- но нулю, посылает требуемое количество байтов из kbufr в последова- тельное устройство. Отметим, что процедура getkbd не использует системную функцию "Читать из файла или устройства" , так как в этом случае процесс пе- рейдет в состояние ожидания до тех пор, пока с клавиатуры не будет введена ключевая строка, в то же время наша программа никогда не долж- на переходить в состояние ожидания. Вместо этого, она использует функ- ции MS-DOS , которые позволяют проверить состояние клавиатуры (функция 0BH) и прочитать значение нажатой клавиши без эхо-сопровождения (функ- ция 07H). Кроме того, осуществляется специальная обработка клавиши Enter (строка 45 - 48): признак новой строки вставляется сразу после Enter, и длина буфера kblen устанавливается равной 2. Ключевая последовательность Ctrl-C завершает работу программы, она распознается процедурой getkbd (строка 41) и вызывает немедленную передачу управления на метку quit (строка 19) в конце основного цикла. Поскольку программа ENGINE использует только постоянно резидентные программы, то нет никакой необходимости снятия ранее произведенных ин- столлирующих действий перед тем, как возвратить управление MS-DOS. Программа ЕNGINE.ASM написана таким образом, чтобы можно было ее использовать как COM-файл. Поэтому ее ассемблирование и разрешение внешних ссылок происходит точно так же, как и при получении COMDVR.SYS, но с использованием расширения СОМ вместо SYS, при этом нет необходимости изменять что-либо в CONFIG.SYS. 6.3. Утилита установления состояния драйвера: CDVUTL.C Программа CDVUTL.C (см. рис.6-4) позволяет произвести реконфигу- рацию двух устройств ASY1 и ASY2 после того, как было уже произведено их инстоллирование. После того, как одно из этих устройств было специ- фировано (порт1 или порт2), уровень передачи данных, длина слова, па- ритет и количество стоп-битов могут быть изменены; каждое изменение осуществляется независимо и не влияет на характеристики других пара- метров. Кроме того, управление потоком можно переключать на два типа аппаратного режима синхронизации передачи данных - с включенным режи- мом XON/XOFF или с выключенным режимом,- вывод сообщений об ошибках может осуществляться с использованием операции, ориентированной на вы- вод символов или сообщений. 1: /* cdvutl.c - COMDVR Utility 2: * Jim Kyle - 1987 3: * используется с Драйвером устройства COMDVR.SYS 4: */ 5: 6: #include /* дефиниции для в/в */ 7: #include /* управление в/в */ 8: #include /* */ 9: #include /* */ 10: 11: /* определение битов состояния драйвера */ 12: 13: #define HWINT 0X0800 /* MCR,первое слово,прерывание HW */ 14: #define O_DTR 0X0200 /* MCR,первое слово,вывод DTR */ 15: #define O_RTS 0X0100 /* MCR,первое слово,вывод RTS */ 16: 17: #define m_PG 0X0010 /* LCR,первое слово,паритет ON */ 18: #define m_PE 0X0008 /* LCR,первое слово,паритет EVEN */ 19: #define m_XS 0X0004 /* LCR,первое слово,2 стоп-бита */ 20: #define m_WL 0X0003 /* LCR,первое слово, */ 21: 22: #define i_CD 0X8000 /* MSR,второе слово,Установлен сигнал */ 23: #define i_RI 0X4000 /* MSR,второе слово,Индикатор Звука */ 24: #define i_DSR 0X2000 /* MSR,второе слово,Готовность данных */ 25: #define i_CTS 0X1000 /* MSR,второе слово,Очистить для посылки*/ 26: 27: #define i_SRE 0X0040 /* LSR,второе слово,Xmtr SR пустой */ 28: #define i_HRE 0X0020 /* LSR,второе слово,Xmtr HR пустой */ 29: #define i_BRK 0X0010 /* LSR,второе слово,получен сигнал BREAK*/ 30: #define i_ER1 0X0008 /* LSR,второе слово,FrmErr */ 31: #define i_ER2 0X0004 /* LSR,второе слово,ParErr */ 32: #define i_ER3 0X0002 /* LSR,второе слово,OveRun */ 33: #define i_RRF 0X0001 /* LSR,второе слово,Регистр-приемник полный*/ 34: 35: /* теперь определим для драйвера ASNI.SYS строку очистки экрана */ 36: #define СLS "\033[2J" 37: 38: File *dvp; 39: union REGS rvs; 40: int iobf[5]; 41: 42: main() 43: { cputs ("\n CDVUTL - COMDVR Utility Version 1.0 - 1989\n"); 44: disp(); /* выполнить цикл планирования */ 45: } 46: 47: disp() /* Планировщик */ 48: { int c, 49: u; 50: u=1; 51: while (1) 52: { cputs("\r\n\t Команда (? для помощи):"); 53: switch (tolower(c=getche()) /* планирование */ 54: { 55: case '1' : /* выбрать порт 1 */ 56: fclose(dvp); 57: dvp=fopen("ASY1","rb+"); 58: u=1; 59: break; 60: 61: case '2' : /* выбрать порт 2 */ 62: fclose(dvp); 63: dvp=fopen("ASY2","rb+"); 64: u=2; 65: break; 66: 67: case 'b' : /* установить уровень передачи */ 68: if (iobf[4] == 300) 69: iobf[4]=1200; 70: else 71: if (iobf[4] == 1200) 72: iobf[4]=2400; 73: else 74: if (iobf[4] == 2400) 75: iobf[4]=9600; 76: else 77: iobf[4] = 300; 78: iocwr(); 79: break; 80: 81: case 'e': /* установить контроль четности */ 82: iobf[0]|=(m_PG+m_PE); 83: iocwr(); 84: break; 85: 86: case 'f' : /* переключить управление потоком */ 87: if (iobf[3] == 1) 88: iobf[3]=2; 89: else 90: if (iobf[3] == 2) 91: iobf[3]=4; 92: else 93: if (iobf[3] == 4) 94: iobf[3]=0; 95: else 96: iobf[3] = 1; 97: iocwr(); 98: break; 99: 100: case 'i': /* инициализация MSR/LSR к */ 101: iobf[0]=(HWINT + o_DTR + o_RTS + m_WL); 102: iocwr(); 103: break; 104: 105: case '?' : /* помощь */ 106: cputs(cls); /* очистить экран */ 107: center("СПИСОК КОМАНД \n"); 108: center("1=выбрать порт 1 L=переключить длину слова"); 109: center("2=выбрать порт 2 N=установить контроль четности NONE")' 110: center("В=уст.уровень передачи О=уст.контроль четности ODD"); 111: center("E=уст.контроль четности EVEN R=откл.сообщения об ошибках"); 112: center("F=переключить управление потоком S=переключить стоп-биты"); 113: center("I=инициализировать .... Q=выход"); 114: continue; 115: 116: case 'l': /* переключить длину слова */ 117: iobf[0] ^= 1; 118: iocwr(); 119: break; 120: 121: case 'n': /* выключить контроль четности */ 122: iobf[0] &=~ (m_PG + m_PE); 123: iocwr(); 124: break; 125: 126: case 'o': /* установить контроль четности ODD */ 127: iobf[0] ^= m_PG; 128: iobf[0] &=~ m_PE; 129: iocwr(); 130: break; 131: 132: case 'r': /* переключить сообщения об ошибках */ 133: iobf[0] ^= 1; 134: iocwr(); 135: break; 136: 137: case 's': /* переключить стоп-биты */ 138: iobf[0] ^= m_XS; 139: iocwr(); 140: break; 141: 142: case 'q': /* выйти из цикла и завершить работу */ 144: fclose(dvp); 145: exit(0); 145: } 146: cputs(cls); /* очистить экран */ 147: center("ТЕКУЩЕЕ СОСТОЯНИЕ COMVDR \n"); 148: report(u,dvp); /* отчет текущего состояния */ 149: } 150: } 151: 152: center(s) char *s; /* центровка строки на экране */ 153: { int i; 154: for(i=80 - strlen(s); i>0; i-=2) 155: putch(' '); 156: cputs(s); 157: cputs("\r\n"); 158: } 159: 160: iocwr() /* IOCTL Write к COMDVR */ 161: { rvs.x.ax = 0x4403; 162: rvs.x.bx = fileno(dvp); 163: rvs.x.cx = 10; 164: rvs.x.dx = (int)iobf; 165: intdos(&rvs,&rvs); 166: } 167: 168: char *onoff(x) int x; 169: { return(x? "ВКЛ": "ВЫКЛ"); 170: } 171: 172: report (unit,dvp) int unit; 173: { char temp[80]; 174: rvs.x.ax = 0x4402 175: rvs.x.bx = fileno(dvp); 176: rvs.x.cx = 10; 177: rvs.x.dx = (int)iobf; 178: intdos(&rvs,&rvs); /* использование функции DOS IOCTL Read для получения данных о драйвере*/ 179: sprint(temp,"\n Устройство ASY %d\t%d BPS, %d-c-%c\r\n\n", 180: unit,iobf[4], /* уровень передачи */ 181: 5+(iobf[0]&m_WL), /* длина слова */ 182: (iobf[0] & m_PG ? 183: (iobf[0]&m_PE ? 'E' :'O') :'N'), /* контроль четности */ 184: (iobf[0]&m_XS ? '2': '1')); /* стоп-биты */ 185: cputs(temp); 186: 187: cputs("Аппаратные прерывания") 188: cputs(onoff(iobf[1] & HWINT)); 189: cputs(", Готовность терминала") 190: cputs(onoff(iobf[1] & o_DTR)); 191: cputs(", Запрос на передачу") 192: cputs(onoff(iobf[1] & o_RTS)); 193: cputs(",\r\n"); 194: 195: cputs("Детектор носителя данных") 196: cputs(onoff(iobf[1] & i_CD)); 197: cputs(",Готовность модема") 198: cputs(onoff(iobf[1] & i_DSR)); 199: cputs(",Сброс передатчика") 200: cputs(onoff(iobf[1] & i_CTS)); 201: cputs(",кольцевой индикатор"); 202: cputs(onoff(iobf[1] & i_RI)); 203: cputs(",\r\n"); 204: 205: cputs(i_SRE & iobf[1] ? "Xmtr SR пустой,":""); 206: cputs(i_HRE & iobf[1] ? "Xmtr HR пустой,":""); 207: cputs(i_BRK & iobf[1] ? "Получен сигнал BREAK,":""); 208: cputs(i_ER1 & iobf[1] ? "Ошибка оформления пакета,":""); 209: cputs(i_ER2 & iobf[1] ? "Ошибка паритета,":""); 210: cputs(i_ER3 & iobf[1] ? "Ошибка переполнения,":""); 211: cputs(i_RRF & iobf[1] ? "Регистр-приемник полный,":""); 212: cputs("\b\b.\r\n"); 213: 214: cputs ("Прием ошибок"); 215: if (iobf[2]==1) 216: cputs("Осуществляется посредством занесения в буфер соотв.сооб."); 217: else 218: cputs ("Осуществляется посредством установления соотв.флага"); 219: cputs(".\r\n"); 220: 221: cputs ("Управление потоком передачи"); 222: if (iobf[3]&4) 223: cputs ("Осуществляется посредством сигналов XON или XOFF"); 224: else 225: if (iobf[3]&2) 226: cputs ("Осуществляется посредством сигналов RTS (Запрос пере- датчика) и CTS (Сброс передатчика)"); 227: else 228: if (iobf[3]&1) 229: cputs ("Осуществляется посредством сигналов DTR (Готовность терминала) и DSR (Готовность модема)"); 230: else 231: cputs (" не разрешен"); 232: cputs(".\r\n"); 233: } 234: 235: /* конец программы CDVUTL.C*/ Рис.6-4. CDVUTL.C Наибольшие сложности CDVUTL сосредоточены в той ее части, которая отображает установочные биты драйвера в соответствующий текст, выводи- мый на экран дисплея. Каждое такое отображение занимает в исходном тексте программы несколько строк для генерации лишь нескольких слов выводимого на экран отчета. В таблице 6-10 подытожены функции, входящие в программу CDVUTL. Таблица 6-10 Функции программы CDVUTL ___________________________________________________________________ | | | | | Строки | Имя | Описание | |__________|_________|______________________________________________| | | | | | 42 - 45 | main() | Стандартная точка входа | | 47 - 150 | disp() | Планировщик | |152 - 158 | center()| Центровка текста на экране | |160 - 166 | ioctwr()| Запись управляющей последовательности в драй-| | | | вер с помощью функции IOCTL WRITE | |168 - 170 | onoff() | Возвращение значения либо ON либо OFF | |172 - 233 | report()| Чтение состояния драйвера и вывод отчета на | | | | экран | |__________|_________|______________________________________________| Длинный список операторов #define в начале исходного текста (строки 11 - 33) облегчает восприятие отображения битов драйвера за счет назначения символьного имени каждому значащему биту для четырех регистров устройства UART. Подпрограмма main() выводит заголовок программы CDVUTL и затем обращается к подпрограмме "Планировщик" (disp()), которая собственно и начинает выполнение всех действий. Программа CDVUTL не использует па- раметры, задаваемые в командной строке, и информацию по среде, поэтому обычное объявление аргументов при вызове программы на выполнение опус- кается. Первым действием подпрограммы disp() является установление драй- вера (по умолчанию) как ASY1. В этом случае u=1 и открывается ASY1 (строка 50). Затем программа начинает выполнение внутреннего цикла (строки 51 - 149). Каждое повторение этого цикла сопровождается выводом на экран подсказки, являющейся для пользователя признаком того, что необходимо ввести очередную команду (строка 52). Затем считывается ключевое сло- во, которое используется огромным оператором switch() (строки 53-1430). Если введенное ключевое слово не совпадает ни с одним из до- пустимых случаев оператора switch, то никакие действия не выполняются. В этом случае программа просто выводит на экран отчет о текущем состо- янии драйвера и повторно переходит в начало цикла (строки 146 - 148). Если , однако, значение нажатой клавиши совпадает с одним из воз- можных случаев оператора switch(), то выполняется соответствующая ко- манда. Цифры 1 (строка 55) и 2 (строка 61) определяют тот драйвер, на который осуществляется воздействие. Клавиша "?" (строка 105) вызывает вывод на экран дисплея списка значений командных ключей. Клавиша "q" (строка 142) вызывает завершение программы, инициируя обращение к фун- кции exit(); эта команда предоставляет единственную возможность завер- шить бесконечный цикл. Все остальные командные ключи вызывают измене- ния одного или большего количества битов в управляющей строке IOCTL, которая затем посылается драйверу с помощью функции MS-DOS IOCTL Write (прерывание 21H, функция 44h, подфункция 03h). Эти действия осуществ- ляются процедурой iocwr() (строки 160 - 166). После выполнения команды (за исключением команды "q", которая за- вершает работу CDVUTL и возвращает управление MS-DOS на командный уро- вень, и команды "?", которая выводит на экран дисплея список значений командных ключей) вызывается (строка 148) функция report() (строки 172 - 233), которая выводит на экран дисплея все драйверные атрибуты, включая и только что измененные Эта функция (report()) вызывает функ- цию MS-DOS IOCTL Read (прерывание 21H, функция 44H, подфункция 02H, строки 174 - 178), которая читает информацию о новом состоянии драйве- ра в управляющую строку и затем использует некоторую последователь- ность фильтрующих битов (строки 179 - 232) для перевода полученной ин- формации о состоянии в соответствующие сообщения, выводимые на экран дисплея. В программе CDVUTL были широко использованы программы ввода/выво- да для работы с консолью, которые поставляются вместе с компилятором Microsoft C в соответствующих библиотеках. Поэтому при использовании других компиляторов C могут потребоваться изменения в названиях этих библиотечных программ, а также в названиях соответствующих файлов, загружаемых по команде #include (строки 6 - 9). Каждая из реальных последовательностей команд изменяет только несколько битов в одном из 10 байтов командной строки и затем записы- вает эту строку в драйвер. Некоторая коммуникационная программа, обла- дающая более полными возможностями, может сделать различные изменения битов сразу - например, переключение от 7-битного кода, паритет по четности, отсутствие паритета и управлению потоком на основе XON/XOF к 8-битному коду, управление потоком для предотвращения потери байтов со значениями 11H и 13H при передаче файлов в протоколе коррекции ошибок. В этом случае эта программа может сделать все необходимые изменения в управлящей строке перед вызовом единственной функции IOCTL Write. 7. Традиционный подход Поскольку необходимый драйвер устройства никогда не был частью MS DOS, то большинство коммуникационных программ написаны таким образом, чтобы обеспечить собственные средства инстолляции драйвера и последую- щего удаления его перед возвратом управления в MS-DOS . В этой статье приводится пример программного пакета, иллюстрирующего такой подход. Хотя основная часть пакета написана на Microsoft C, в нем есть три мо- дуля, написанных на ассемблере. Эти модули состоят из программ, обес- печивающих обслуживание аппаратных прерываний, программ обработки осо- бых ситуаций и программ быстрого взаимодействия с дисплеем. Вначале будут рассмотрены именно эти модули. 7.1. Модуль обслуживания аппаратных прерываний (ISR) Первым является модуль обслуживания прерываний устройства UART. Его текст приводится на Рис.6-5 (программа CH1.ASM, написанная на ас- семблере) 1: TITLE CH1.ASM 2: 3: ; CH1.ASM -- 4: ; обеспечивает работу только с СОМ2 5: ; используется только с Microsoft С и малой моделью памяти 6: 7: _TEXT segment byte public 'CODE' 8: _TEXT ends 9: _DATA segment byte public 'DATA' 10: _DATA ends 11: CONST segment byte public 'CONST' 12: CONST ends 13: _BSS segment byte public 'BSS' 14: _BSS ends 15: 16: DGROUP GROUP CONST, _BSS, _DATA 17: ASSUME CS:_TEXT, DS:DGROUP, ES:DGROUP, SS:DGROUP 18: 19: _TEXT SEGMENT 20: 21: PUBLIC _i_m,_rdmdm,_Send_Byte,_wrtmdm,_set_mdm,_u_m 22: 23: bport EQU 02F8h ;базовый адрес СОМ2 (СОМ1 используется ; 03F8H) 24: getiv EQU 350Bh ;получить вектор прерывания для СОМ2 (для ; СОМ1 используйте 0Ch) 25: putiv EQU 250Bh ;записать вектор прерывания для СОМ2 26: imrmsk EQU 00001000b ;маска СОМ2 (для СОМ1 используйте 0000100b) 27: oiv_o DW 0 ;область для сохранения старого вектора 28: oiv_s DW 0 ; прерывания 29: 30: bf_pp DW in_bf ;указатель вывода(последний используемый) 31: bf_gp DW in_bf ;указатель ввода(следующий для использо- ; вания) 32: bf_bg DW in_bf ;начало буфера 33: bf_fi DW b_last ;конец буфера 34: 35: in_bf DB 512 DUP (?) ;буфер ввода 36: 37: b_last EQU $ ;адрес первого байта памяти после буфера 38: 39: bd_dv DW 0417h ;делители уровня передачи (0=110 bps) 40: DW 0300h ;код 1 = 150 bps 41: DW 0180h ;код 2 = 300 bps 42: DW 00C0h ;код 3 = 600 bps 43: DW 0060h ;код 4 = 1200 bps 44: DW 0030h ;код 5 = 2400 bps 45: DW 0018h ;код 6 = 4800 bps 46: DW 000Ch ;код 7 = 9600 bps 47: 48:_set_mdm proc near ;замещает функцию BIOS 'init' 49: push bp 50: mov bp,sp ;установить указатель фрейма стека 51: push es ;сохранить регистры 52: push ds 53: mov ax,cs ;установка этих регистров к сег- 54: mov ds,ax ; менту CODE 55: mov es,ax ; 56: mov ah,[bp+4] ;получить параметр,переданный ; от C-программы 57: mov dx,bport+3 ;установить указатель на LCR 58: mov al,80h ;установить бит DLAB 59: out dx,al 60: mov dl,ah ;сдвинуть параметр к полю BAUD 61: mov cl,4 62: rol dl,cl 63: and dx,00001110b ;замаскировать все другие 64: mov di,offset bd_dv ; биты 65: add di,dx ;построить указатель к делителю 66: mov dx,bport+1 ;установить старший бит 67: mov al,[di+1] 68: out dx,al ;записать старший байт в UART 69: mov dx,bport ;затем младший байт 70: mov al,[di] 71: out dx,al 72: mov al,ah ; 73: and al,00011111b ; 74: mov dx,bport+3 75: out dx,al 76: mov dx,bport+2 ; 77: mov al,1 ;получить только тип 78: out dx,al 79: pop ds ;восстановить сохраненные регистры 80: pop es 81: mov sp,bp 82: pop bp 83: ret 84:_set_mdm endp 85: 86: _wrtmdm proc near ;Запись символа в модем 87: _send_byte: ;имя,которое используется в основной 88: push bp ; программе 89: mov bp,sp ;установить указатель фрейма стека 90: push es ;сохранить регистры 91: push ds 92: mov ax,cs 93: mov ds,ax 94: mov es,ax ; 95: mov dx,bport+4 ;установить DTR, RTS и OUT 96: mov al,0bh 97: out dx,al 98: mov dx,bport+6 ; 99: mov bh,30h 100: call w_tmr 101: jnz w_out ; 102: mov dx,bport+5 ;проверка готовности UART 103: mov bh,20h 104: call w_tmr 105: jnz w_out ;тайм-аут 106: mov dx,bport ;послать в UART 107: mov al,[bp+4] ;символ пришедший из С 108: out dx,al 109: w_out pop ds ;восстановить сохраненные регистры 110: pop es 111: mov sp,bp 112: pop bp 113: ret 114: _wrtmdm endp 115: 116: _rdmdm proc near ;Читает байт из буфера 117: push bp 118: mov bp,sp ;установить указатель фрейма стека 119: push es ;сохранить регистры 120: push ds 121: mov ax,cs 122: mov ds,ax 123: mov es,ax ; 124: mov ax,0ffffh ;установить EOF 125: mov bx, bf_gp ;использовать указатель "get" 126: cmp bx,bf_pp ;сравнить с указателем "put" 127: jz nochr ;равны, буфер пуст 128: inc bx ;иначе символ в буфере доступен 129: cmp bx,bf_fi ;конец буфера? 130: jnz noend ;нет 131: mov bx, bf_gr ;да, стать в начало буфера 132: noend: mov al,[bx] ;получить символ 133: mov bf_gp,bx ;обновить указатель "get" 134: inc ax, ;обнулить АН (АН используется как флаг) 135: nochr: pop ds ;восстановить сохраненные регистры 136: pop es 137: mov sp,bp 138: pop bp 139: ret 140: _rdmdm endp 141: 142: w_tmr proc near 143: mov bl,1 ;ожидать таймера; двойной цикл 144: w_tm1: sub cx,cx ;установка для внутреннего цикла 145: w_tm2: in al,dx ;проверить затребованный ответ 146: mov ah,al ;сохранить то, что пришло 147: and al,bh ;выделить только нужные биты 148: cmp al,bh ;и затем сравнить 149: jz w_tm3 ;ответ получен, выход с флагом ZF 150: loop w_tm2 ;иначе попытаться повторить 151: dec bl ;пока не исчерпался двойной цикл 152: jnz w_tm1 153: or bh,bh ;тайм_аут выход с флагом NZ 154: w_tm3: ret 155: w_tmr endp 156: 157: ; Программа обслуживания аппаратного прерывания 158: rts_m: cli 159: push ds ;сохранить регистры 160: push ax 161: push bx 162: push cx 163: push cs 164: push ds ;установить DS=CS 165: pop ds 166: mov dx,bport ;попытка получить символ из UART 167: in al,dx 168: mov bx, bf_pp ;использовать указатель "put" 169: inc bx ;передвинуться к следующему входу 170: cmp bx,bf_fi ;конец уже достигнут? 171: jnz nofix ;нет 172: mov bx, bf_pg ;да, установить на начало буфера 173: nofix: mov [bx],al ;записать символ в буфер 174: mov bf_pp,bx ;обновить указатель "put" 175: mov al,20h ;послать EOI в устройство 8259 176: out 20h,al 177: pop dx ;восстановить сохраненные регистры 178: pop cx 179: pop bx 180: pop ax 181: pop ds 182: iret 183: 184: _i_m proc near ;Инстoллирование программы обслуживания 185: push bp ; модема 186: mov bp,sp ;установить указатель фрейма стека 187: push es ;сохранить регистры 188: push ds 189: mov ax,cs 190: mov ds,ax ;установить DS и ES равными CS 191: mov es,ax ; 192: mov dx,bport+1 ;установить IER 193: mov al, 0Fh ;разрешить все прерывания 194: out dx,al 195: 196: im1: mov dx,bport+2 ;почистить все старые требования к UART 197: in al,dx ;прочитать регистр IID 198: mov ah,al ;сохранить то, что там было 199: test al,1 ;в IID что-то еще есть не обработанное 200: jnz im5 ;нет , все теперь очищено 201: cmp ah,0 ;да, прерыание по состоянию модема 202: jnz im2 ;нет 203: mov dx,bport+6 ;да,читать MSR,чтобы очистить его 204: in al,dx ; 205: im2: cmp ah,2 ;регистр HR пуст? 206: inz im3 ;нет, никакие действия не нужны 207: im3: cmp ah,4 ;прерывание по готовности"данные получены?" 208: jnz im4 ;нет 209: mov dx,bport ;да,прочитать их,чтобы сбросить это 210: in al,dx ; состояние 211: im4: cmp ah,6 ;прерывание по LSR 212: jnz im1 ;нет, проверять дальше 213: mov dx,bport+5 ;да, чтобы очистить LSR необходимо 214: in al,dx ; его прочитать 215: jmp im1 ;повторить проверку 216: 217: im5: mov dx,bport+4 ;обеспечить рабочие условия 218: mov al,0bh ;установить биты DTR, RTS, OUT2 219: out dx,al 220: mov al,1 ;разрешить только прерывание RCV 221: mov dx,bport+1 222: out dx,al 223: mov ax,getiv ;получить старый вектор прерыания 224: int 21h 225: mov oiv_o,bx ;и сохранить его для последующего 226: mov oiv_s,es ;восстановления 227: mov dx,offset rts_m ;установить новый вектор прерыва- 228: mov ax,putiv ; ния 229: int 21h 230: in al,21h ;теперь разрешить 231: and al, not imrmsk 232: out 21h,al 233: mov al,20h ;затем послать сигнал EOI 234: out 20h,al 235: pop ds ;восстановить регистры 236: pop es 237: mov sp,bp 238: pop bp 239: ret 240: _i_m endp 241: 242: _u_m proc near ;разинстoллирование программы обслуживания 243: push bp ; модема 244: mov bp,sp ;сохранение регистров 245: in al,21h ;запретить прерывания от СОМ в 8259 246: or al, imrmsk 232: out 21h,al 248: push es 249: push ds 250: mov ax,cs 251: mov ds,ax 252: mov es,ax 253: mov al, 0 ;запретить прерывания UART 254: mov dx,bport+1 255: out dx,al 256: mov dx,oiv_o ;восстановить старый вектор прерывания 257: mov ds,oiv_s 258: mov ax,putiv 259: int 21h 260: pop ds ;восстановить регистры 261: pop es 262: mov sp,bp 263: pop bp 264: ret 265: _u_m endp 266: 267: _TEXT ends 268: 269: END Рис.6-5 СH1.ASM Таблица 6-11 Функции модуля СH1 __________________________________________________________________ | | | | | Строки | Имя | Описание | |________|____________|____________________________________________| | | | | | 1 - 26 | | Административные детали | | 27- 46 | | Области данных | | 48- 84 | _set_mdm | Инициализация UART параметрами, переданны-| | | |ми от С-программы | | 86-114 | _wrtmdm | Вывод символа в UART | | 87 | _send_byte | Точка входа, используемая, если к системе | | | |добавлено управление потоком | |116-140 | _rdmdm | Выбрать символ из буфера, куда его записа-| | | |ла программа обработки прерывания или вер- | | | |нуть признак отсутствия доступного символа | |142-155 | w_tmr | Ждать таймер; внутренная программа, испо- | | | |льзуемая для предовращения вечного ожидания | | | |в случае возниконовения неисправностей в ка-| | | |налах передачи данных | |157-182 | rts_m | Программа обработки аппаратного прерывания| | | |(ISR); эта программа инстоллируется посред-| | | |ством программы _i_m и удаляется программой| | | |_u_m | |184-240 | _i_m | Программа инстоллирования ISR; сохраняет | | | |старый вектор прерывания | |242-265 | _u_m | Программа деинстоллирования ISR; восста- | | | |навливает старый вектор прерывания | |________|____________|____________________________________________| Программы, находящиеся в модуле СH1, настроены на работу только с COM2; для использования их с COM1 необходимо изменить значения трех символьных констант: BPORT , GETIV и PUTIV . Представленный здесь код расчитан на использование только с малой (Small) моделью компилятора Microsoft C. Чтобы обеспечить его работу с другими моделями памяти не- обходимо обратиться к соответствующему руководству по компилятору С. См. также "Программирование в среде MS-DOS. Программирование для MS DOS. Структура программы приложения." В таблице 6-11 приведен список всех частей модуля CH1 в том по- рядке, как они встречаются в листинге программы. Лидирующий знак под- черкивания в именах шести функций необходим для совместимости с С ком- пилятором. Внутри программы, написанной на С, обращение к этим функци- ям осуществляется путем указания имени одной из этих функций с опущен- ным лидирующим символом подчеркивания. Для упрощения, используемая в этом примере программа ISR ( в от- личие от драйвера устройства) обслуживает только прерывания по получе- нию данных. Остальные типы прерываний UART запрещены. Всякий раз, ког- да от UART поступает байт данных, программа ISR заносит его в буфер. Программа _rdmdm, вызываемая из С-программы, считывает из буфера этот байт (если он доступен) или возвращает в С-программу код EOF (-1), чтобы сообщить об отсутствии доступного (в буфере) символа. Чтобы послать байт, С-программа может вызвать либо _send_byte ли- бо_wrmdm. В приведенном пакете эти имена указывают на одну и ту же программу. В более сложной программе, из которой данный пакет был адаптирован для этой книги, _send_byte вызывалась, когда запрашивалось управление потоком, и программа управления потоком вызывает _wrtmdm. Для того, чтобы использовать управление потоком в CH1.ASM требуется удалить строку 87, а к основной С-программе должна быть добавлена фун- кция управления с именем send_byte() . Анализ действий по управлению потоком происходит в send_byte(); _wrtmdm осуществляет взаимодействие с реальным портом. Таблица 6-12 Значения параметра для _set_mdm ___________________________________________________________________ | | | | Двоичный код | Значение | |____________________|______________________________________________| | 000ХХХХХ | Установить уровень передачи 110 bps | | 001ХХХХХ | Установить уровень передачи 150 bps | | 010ХХХХХ | Установить уровень передачи 300 bps | | 011ХХХХХ | Установить уровень передачи 600 bps | | 100ХХХХХ | Установить уровень передачи 1200 bps | | 101ХХХХХ | Установить уровень передачи 2400 bps | | 110ХХХХХ | Установить уровень передачи 4800 bps | | 111ХХХХХ | Установить уровень передачи 9600 bps | | ХХХХ0ХХХ | Нет контроля четности | | ХХХ01ХХХ | Контроль четности на "Нечетность" | | ХХХ11ХХХ | Контроль четности на "Четность" | | ХХХХХ0ХХ | 1 стоп-бит | | ХХХХХ1ХХ | 2 стоп-бита (1,5 если WL=5) | | ХХХХХХ00 | Длина слова = 5 | | ХХХХХХ01 | Длина слова = 6 | | ХХХХХХ10 | Длина слова = 7 | | ХХХХХХ11 | Длина слова = 8 | |____________________|______________________________________________| Чтобы установить уровень передачи в бодах, длину слова, контроль четности, С-программа вызывает _set_mdm и передает ей в качества аргу- мента установочный параметр. Формат этого параметра, приведенный в таблице 6-12, идентичен прерыванию 14H с функцией 00H (Инициализация) системы IBM BIOS. Программа CH1.ASM обеспечивает для входных данных циклический бу- фер размером 512 байт. Такой размер является вполне адекватным для уровня передачи в 2400 bps без потери данных во время скроллинга. 7.2. Модуль обработки особых ситуаций Для успешной работы программы обслуживания прерываний CH1 в ней должна быть предусмотрена обработка особых ситуаций, которая предотв- ращает возврат управления MS-DOS до того, как программа _u_m восстано- вит вектор прерываний к его первоначальному виду. Если произойдет воз- врат управления MS-DOS без вызова _u_m, то скорее всего это вызовет крах системы, когда последующее прерывание по передаче данных потребу- ет своей обработки, а корректной программы ISR уже нет в памяти. Выполнение обработки особых ситуаций (CH1.ASM), включающее прог- раммы установки, доступа и удаления приведено на рис.6-6. Подобно ISR этот модуль построен таким образом, чтобы он мог работать с Microsoft C (только с моделью памяти типа Small). 1: TITLE CH1A.ASM 2: 3: ; CH1A.ASM - обеспечивает работу эмулятора терминала CTERM.C 4: ; замещает программы обработки Ctrl-C/Ctrl-BREAK 5: ; использование: void set_int(); rst_int(); 6: ; int broke(); /* */ 7: ; Используется с Microsoft C и моделью памяти SMALL 8: 9: _TEXT segment byte public 'CODE' 10: _TEXT ends 11: _DATA segment byte public 'DATA' 12: _DATA ends 13: CONST segment byte public 'CONST' 14: CONST ends 15: _BSS segment byte public 'BSS' 16: _BSS ends 17: 18: DGROUP GROUP CONST, _BSS, _DATA 19: ASSUME CS:_TEXT, DS:DGROUP, ES:DGROUP, SS:DGROUP 20: 21: _DATA SEGMENT BYTE PUBLIC 'DATA' 22: 23:OLDINT1B DD 0 ;область сохранения оригинального вектора пре- 24: ; рываний INT 18h 25: _DATA ENDS 26: 27: _TEXT SEGMENT 28: 29: PUBLIC _set_int,_rst_int,-broke 30: 31: myint1B: 32: mov word ptr cs:brkflg, 1Bh 33: iret 34: 35: myint23: 36: mov word ptr cs:brkflg, 23h 37: iret 38: 39: brkflg dw 40: 41: _broke proc near ; 42: xor ax,ax ; 43: xchg ax,cs:brkflg ; 44: ret 45: _broke endp 46: 47:_set_int proc near 48: mov ax,351Bh ;получить старое 49: int 21h ;прерывание 1Bh 50: mov word ptr oldint1B,bx ; 51: mov word ptr oldint1B+2,es 52: 53: push ds 54: mov ax,cs 55: mov ds,ax ;Установить обработку прерывания 1Bh 56: lea dx,myint1b ;на адрес myint1b (ctrl-break) 57: mov ax,251bh 58: int 21h 59: mov ax,cs 60: mov ds,ax 61: lea dx,myint23 ;Установить обработку прерывания 23h 62: mov ax,2523h ; на адрес myint23 (ctrl-C) 63: int 21h 64 pop ds 65 ret 66:_set_int endp 67: 68:_rst_int proc near 69: push ds 70: lds dx,oldint1B ;Восстановить старый адрес вектора 71: mov ax,251bh ; прерывания 1Bh 72: int 21h 73: pop ds 74: ret 75:_rst_int endp 76: 77: _TEXT ends 78: 79: END Рис.6-6. СH1.ASM З_а_м_е_ч_а_н_и_е: Этот модуль не представляет возможностей обра- ботки фатальных ошибок, которые могут возникнуть при работе дисковода. При возникновении таких ошибок необходим перезапуск системы. См. Прог- раммирование в среде MS-DOS. Настройка MS-DOS. Обработка особых ситуа- ций. CH1A состоит из трех функций. Первая, _set_int, сохраняет старое значение вектора прерывания 1BH (ROM BIOS Control_Break), устанавлива- ет новое значение для этого вектора и для вектора 23Н (адрес обработ- чика Ctrl-C). Вторая функция, _rst_int, восстанавливает значение век- тора 1BH. Третья функция, _broke, возвращает текущее значение внутрен- него флага (этот флаг обнуляется, как только он считывается программой broke). Значение этого флага устанавливается в соответствии с возника- ющими особыми ситуациями (либо Ctrl-C, либо Ctrl-Break) и анализирует- ся в основной С-программе посредством обращения к функции _broke. 7.3. Модуль работы с видеодисплеем Последний модуль, написанный на ассемблере (CH2.ASM) и используе- мый вторым пакетом, приведен на рис.6-7. В этом модуле очистка экрана и позиционирование курсора осуществляется с помощью функций IBM BIOS. Можно исключить вызов функций BIOS, причем для этого потребуется нез- начительная переделка программ, в которых происходит обращение к этим функциям. Так, в оригинальной программе (DT115.EXE системы CLMFORUM фирмы CompuServe), на основе которой написана CTERM, этот модуль обес- печивает оконную технику с дополнительным улучшением скорости работы с дисплеем. 1: TITLE CH2.ASM 2: 3: ; CH2.ASM - обеспечивает работу эмулятора терминала CTERM.C 4: ; Используется с Microsoft C и моделью памяти SMALL 5: 6: _TEXT segment byte public 'CODE' 7: _TEXT ends 8: _DATA segment byte public 'DATA' 9: _DATA ends 10: CONST segment byte public 'CONST' 11: CONST ends 12: _BSS segment byte public 'BSS' 13: _BSS ends 14: 15: DGROUP GROUP CONST,_BSS,_DATA 16: ASSUME CS:_TEXT,DS:DGROUP,ES:DGROUP,SS:DGROUP 17: 18: _TEXT SEGMENT 19: 20: PUBLIC _cls,_color,_deol,_i_v,_key,_wrchr,_wrpos 21: 22: atrib DB 0 ;атрибут 23: _colr DB 0 ;цвет 24: v_bas DW 0 ;сегмент видеоадаптера 25: v_ulc DW 0 ;верхний левый угол экрана 26: v_lrc DW 184Fh ;нижний правый угол экрана 27: v_col DW 0 ;текущая колонка/линия 28: 29: _key proc near ;получить значение клавиши 30: push bp 31: mov ah,1 ;функция BIOS, проверящая состояние 32: int 16h ;буфера клавиатуры 33: mov ax,0FFFFh 34: jz key00 ;ввода не было - вернуть EOF 35: mov ah,0 ;была нажата клавиша - считать ее 36: int 16h ; значение 37: key00: pop bp 38: ret 39: _key endp 40: 41: _wrchr proc near 42: push bp 43: mov bp,sp 44: mov al,[bp+4] ;выбрать символ переданный С-програм- 45: cmp al,' ' ; мой 46: jnb prchr ;отображаемый символ - вывести его 47: cmp al,8 48: jnz notbs 49: dec byte ptr v_col ;реализовать BackSpaсe 50: mov al,byte ptr v_col 51: cmp al,byte ptr v_ulc 52: jb nxt_c ;перейти к следующей колонке 53: jmp norml 54: 55: notbs: cmp al,9 56: jnz notht 57: mov al,byte ptr v_col ;реализовать HTAB 58: add al,8 59: and al,0F8h 60: mov byte ptr v_col,al 61: cmp al,byte ptr v_lrc 62: ja nxt_c 63: jmp SHORT normal 64: 65: notht: cmp al,0Ah 66: jnz notlf 67: mov al,byte ptr v_col+1 ;реализовать переход к но- 68: inc al ; вой строке 69: cmp al,byte ptr v_lrc+1 70: jbe noht1 71: call scrol 72: mov al,byte ptr v_lrc+1 73: noht1: mov byte ptr v_col+1,al 74: jmp SHORT norml 75: 76: notlf: cmp al,0Ch 77: jnz ck_cr 78: call _cls ;реализовать перевод страницы 79: jmp SHORT ignor 80: 81: ck_cr: cmp al,0Dh 82: jnz ignor ;игнорировать все другие CTL символы 83: mov al,byte ptr v_ulc ;реализовать CR 84: mov byte ptr v_col,al 85: jmp SHORT norml 86: 87: prchr: mov ah,_colr ;реализовать вывод символа 88: push ax 89: xor ah,ah 90: mov al,byte ptr v_col+1 91: push ax 92: mov al,byte ptr v_col 93: push ax 94: call wrtvr 95: mov sp,bp 96: nxt_c: inc byte ptr v_col ;продвинуться к следующей колонке 97: mov al,byte ptr v_col 98: cmp al,byte ptr v_lrc 99: jle norml 100: mov al,0Dh 101: push ax 102: call _wrchr 103: pop ax 104: mov al,0Ah 105: push ax 106: call _wrchr 107: pop ax 108: norml: call set_cur 109: ignor: mov sp,bp 110: pop bp 111: ret 112: _wrchr endp 113: 114: _i_v proc near ;установить адрес видеосегмента 115: push bp 116: mov bp,sp 117: mov ax,0B000h ; 118: mov v_bas,ax ; 119: mov sp,bp 120: pop bp 121: ret 122: _i_v endp 123: 124: _wrpos proc near ;установить позицию курсора 125: push bp ;по требованию С-программы 126: mov bp,sp 127: mov dh,[bp+4] ;C-программа передает: номер линии 128: mov dl,[bp+6] ; номер колонки 129: mov v_col,dx ;запомнить новую позицию курсора 130: mov bh,atrib ;установить атрибут 131: mov ah,2 132: push bp 133: int 10h ;установить курсор на экране 134: pop bp 135: mov ax,v_col ;вернуть текущее положение курсора 136: mov sp,bp 137: pop bp 138: ret 139: _wrpos endp 140: 141: set_cur proc near ;установить позицию курсора по v_col 142: push bp 143: mov bp,sp 144: mov dx,v_col 145: mov bh,atrib 146: mov ah,2 147: push bp 148: int 10h 149: pop bp 150: mov ax,v_col 151: mov sp,bp 152: pop bp 153: ret 154: set_cur endp 155: 156: _color proc near ;обращение: _color (sg,bg) 157: push bp 158: mov bp,sp 159: mov ah,[bp+6] ;выбрать цвеи фона (bg) 160: mov al,[bp+4] ;выбрать основной цвет (sg) 161: mov cx,4 162: shl ah,cl 163: and al,0Fh 164: or al,ah ;упаковать цвета в один байт 165: mov _colr,al ;и сохранить для дальнейшего исполь- 166: xor ah,ah ; зования 167: mov sp,bp 168: pop bp 169: ret 170: _color endp 171: 172: scrol proc near ;скроллинг экрана на одну линию вверх 173: push bp 174: mov bp,sp 175: mov al,1 ;количество скроллируемых линий 176: mov cx,v_ulc 177: mov dx,v_lrc 178: mov bh,_colr 179: mov ah,6 180: push bp 181: int 10h ;скроллинг экрана с помощью функции 182: pop bp ; BIOS 183: mov sp,bp 184: pop bp 185: ret 186: scrol endp 187: 188: _cls proc near ;очистить экран 189: push bp 190: mov bp,sp 191: mov al,0 ;очистка экрана с помощью функции 192: mov cx,v_ulc ; BIOS 193: mov v_col,cx 194: mov dx,v_lrc 195: mov bh,_colr 196: mov ah,6 197: push bp 198: int 10h ;скроллинг экрана с помощью функции 199: pop bp ; BIOS 200: call set_cur ;установить курсор в левый верхний 201: mov sp,bp ; угол 202: pop bp 203: ret 204: _cls endp 205: 206: _deol proc near 207: push bp 208: mov bp,sp 209: mov al,' ' 210: mov ah,_colr ;установить BLANK 211: push ax 212: mov al,byte ptr v_col+1 213: xor ah,ah 214: push ax 215: mov al,byte ptr v_col 216: 217: deol1: cmp al,byte ptr v_lrc 218: ja deol2 219: push ax 220: call wrtvr ;вывести пробел 221: pop ax 222: inc al ;следующая колонка 223: jmp deol1 224: 225: deol2: mov ax,v_col 226: mov sp,bp 227: pop bp 228: ret 220: _deol endp 230: 231: wrtvr proc near ; 232: push bp ; 233: mov bp,sp 234: mov dl,[bp+4] ; 235: mov dh,[bp+6] ; 236: mov bx,[bp+8] ; 237: mov al,80 ; 238: mul dh 239: xor dh,dh 240: add ax,dx 241: add ax,ax 242: push es 243: mov di,ax 244: mov ax,v_bas 245: mov es,ax 246: mov ax,bx ;получить данные 247: stosw ;и вывести их на экран 248: pop es 249: mov sp,bp 250: pop bp 251: ret 252: wrtvr endp 253: 254: _TEXT ends 255: 256: END Рис.6-7. СH2.ASM 7.4. Пример интеллектуального эмулятора терминала CTERM.C После ознакомления с работой программ обработки прерывания (CH1), обработки особых ситуаций (CH1A) и работы с дисплеем (CH2) мы можем рассмотреть простую программу эмуляции терминала (CTERM.C). Основные функции этой программы написаны на Microsoft C и приведены на рис.6-8. 1: /* Эмулятор терминала (cterm.c) 2: * Jim Kyle, 1987 3: * 4: * Использует файлы CH1, CH1A и CH2 для поддержки MASM... 5: */ 6: 7: #include 8: #include /* ввод-вывод на консоль */ 9: #include 10: #include /* определяет intdos() */ 11: #include 12: #define BRK 'C'-'@' /* управляющие символы */ 13: #define ESC '['-'@' 14: #define XON 'Q'-'@' 15: #define XOFF 'S'-'@' 16: 17: #define True 1 18: #define False 0 19: 20: #define Is_Function_Key(C) ((C)==ESC) 21: 22: static char capbfr[4096]; /* буфер сохранения */ 23: static int wh, 24: ws; 25: 26: static int I, 27: waitchr=0, 28: vflag=False, 29: capbp, 30: capbc, 31: ch, 32: Want_7_Bit=True, 33: Esc_Seq_State=0; /* переменная состояния */ 34: /* ESC-последовательности */ 35: int _cx, 36: _cy, 37: _atr = 0x07, /* атрибут - белое на черном */ 38: _pag = 0, 39: oldtop = 0, 40: oldbot=0x184f; 41: 42: FILE *in_file=NULL; /* начать с ввода с клавиатуры */ 43: FILE *cap_file=NULL; 44: 45:#include "cterm.h" /* внешние переменные и т.п. */ 46: 47: int Wants_To_Abort() /* проверка на Ctrl-C (Ctrl-BREAK) */ 48: { return broke(); 49: } 50: void 51: 52: main (argc,argv) /* основная программа */ 53: int argc; char *argv[]; 54: { char *cp, 55: *addext(); 56: if (argc > 1) /* проверка наличия файла сценария*/ 57: in_file = fopen(addext(argv[1],".SCR"),"r"); 58: if (argc > 2) /* проверка наличия файла сохранения*/ 59: сap_file = fopen(addext(argv[2],".CAP"),"w"); 60: set_int(); /* инсталлировать модуль CH1 */ 61: set_vid(); /* начальная установка видеоадаптера*/ 62: cls(); /* очистить экран */ 63: cputs("Эмулятор Терминала"); /* вывести название программы */ 64: cputs("\r\nДля внутренних команд использ.\r\n\n"); 65: Want_7_Bit=True; 66: ESC_Seq_State=0; 67: Init_Comm(); /* установить драйвер и т.п. */ 68: while(1) /* основной цикл */ 69: { if ((ch=kb_file()) >0) /* проверить:*/ 70 { if (IS_Function_Key(ch)) 71: { if (docmd()<0) /* команда ?*/ 72: break; 73: } 74: else 75: Send_Byte(ch&0x7F); /* послать ее */ 76: } 77: if ((ch=Read_Modem()) >=0) /*проверить удаленное устройство*/ 78: {if (Want_7_Bit) 79: ch&=0x7F; /* убрать старший бит */ 80: switch(ESC_Seq_State) /* состояние устройства */ 81: { 82: case 0: /* нет ESC-последовательности*/ 83: switch(ch) 84: { 85: case ESC: /* получен символ ESC */ 86: ESC_Seq_State=1; 87: break; 88: 89: default: 90: if (ch==waitchr) /*ждать,если затребовано*/ 91: waitchr=0; 92: if (ch==12) /* очистить экран */ 93: cls(); 94: else 95: if (ch!=127) 96: { putchx((char)ch); /*игнорировать забой*/ 97: put_cap((char)ch); 98: } 99: } 100: break; 101: 102: case 1: /* ESC - выполнить действия по ESC-после-*/ 103: switch(ch) /* довательности */ 104: { 105: case 'A': /* VT52 вверх */ 106: ; /* ничего, кроме */ 107: ESC_Seq_State=0; 108: break; 109: 110: case 'B': /* VT52 вниз */ 111: ; 112: ESC_Seq_State=0; 113: break; 114: 115: case 'C': /* VT52 влево */ 116: ; 117: ESC_Seq_State=0; 118: break; 119: 120: case 'D': /* VT52 вправо */ 121: ; 122: ESC_Seq_State=0; 123: break; 124: 125: case 'E': /* VT52 очистить экран */ 126: cls(); 127: ESC_Seq_State=0; 128: break; 129: 130: case 'H': /* VT52 переместить курсор в левый */ 131: locate(0,0); /* верх.угол */ 132: ESC_Seq_State=0; 133: break; 134: 135: case 'j': /* VT52 очистить */ 136: deos(); 137: ESC_Seq_State=0; 138: break; 139: 140: case 'f': /*ANSI.SYS - VT100 последовательность*/ 141: ESC_Seq_State=2; 142: break; 143: 144: default: 145: putchx(ESC); 146: put_cap((char)ch); 147: ESC_Seq_State=0; 148: } 149: break; 150: 151: case 2: 152: ESC_Seq_State=0; /* не выполняется */ 153: } 154: } 155: if (broke()) 156: { cputs("\r\n***BREAK***\r\n"); 157: break; 158: } 159: } /* конец основного цикла */ 160: if (cap_file) 161: cap_flush(); 162: term_comm(); /* восстановить после обработки */ 163: rst_int (); 164: exit(0); /* нормальный возврат в MS DOS */ 165: } 166: 167: docmd() /*локальная командная оболочка */ 168: { FILE *getfil(); 169: int wp; 170: wp=True; 171: if (!in_file || vflag) 172: cputs("\r\n\tCommand:"); /* запросить команду */ 173: else 174: wp=False; 175: ch=toupper(kbd_wait()); /* получить ответ */ 176: if (wp) 177: putchx((char)ch); 178: switch (ch) 179: { 180: case 'S': 181: if (wp) 182: cputs("low speed\r\n"); 183: set_Baud(300); 184: break; 185: 186: case 'D': 187: if (wp) 188: cputs("elay(1-9 sec):"); 189: ch=kbd_wait(); 190: if (wp) 191: putchx((char)ch); 192: delay(1000*(ch-'0')); 193: if (wp) 194: putchx('\n'); 195: break; 196: 197: case 'E': 198: if (wp) 199: cputs("ven Parity\r\n"); 200: set_Parity(2); 201: break; 202: 203: case 'F': 204: if (wp) 205: cputs("set speed\r\n"); 206: set_Baud(1200); 207: break; 208: 209: case 'H': 210: if (wp) 211: { cputs("\r\n\tVALID COMMANDS:\r\n"); 212: cputs("\tD=delay 0-9 seconds.\r\n"); 213: cputs("\tE=even parity.\r\n"); 214: cputs("\tF=(fast)1200-baud.\r\n"); 215: cputs("\tN=no parity.\r\n"); 216: cputs("\tO=odd parity.\r\n"); 217: cputs("\tQ=quit.return to DOS.\r\n"); 218: cputs("\tR=reset modem.\r\n"); 219: cputs("\tS=(slow)300-baud.\r\n"); 220: cputs("\tU=use script file.\r\n"); 221: cputs("\tV=verify file input.\r\n"); 222: cputs("\tW=wait for char."); 233: } 234: break; 235: 236: case 'N': 237: if (wp) 238: cputs("o Parity\r\n"); 229: set_Parity(1); 230: break; 231: 232: case 'O': 233: if (wp) 234: cputs("dd Parity\r\n"); 235: set_Parity(3); 236: break; 237: 238: case 'R': 239: if (wp) 240: cputs("ESET Comm Port\r\n"); 241: Init_Comm(); 242: break; 243: 244: case 'Q': 245: if (wp) 246: cputs(" = Quit Command\r\n"); 247: ch=(-1); 248: break; 249: 250: case 'U': 251: if (in_file && !vflag) 252: putchx('U'); 253: cputs("se file:"); 254: getfil(); 255: cputs("File "); 256: cputs(in_file? :"Open\r\n":"Bad\r\n"); 257: waitchr=0; 258: break; 259: 260: case 'V': 261: if (wp) 262: { cputs("erify flag toggled"); 263: cputs(vflag? "OFF\r\n":"ON\r\n"); 264: } 265: vflag=vflag? False: True; 266: break; 267: 268: case 'W': 269: if (wp) 270: cputs("ait for:<"); 271: waitchr=kbd_wait(); 272: if (waitchr==' ') 273: waitchr=0; 274: if (wp) 275: { if (waitchr) 276: putchx((char)waitchr); 277: else 278: cputs("no wait"); 279: cputs(">\r\n"); 280: } 281: break; 282: 283: default: 284: if (wp) 285: { cputs("Don't know"); 286: putchx((char)ch); 287: cputs("\r\nUse 'H' command for Help.\r\n"); 288: } 289: ch='?'; 290: } 291: if (wp) /* если открывается окно...*/ 292: { cputs("\r\n [any key]\r"); 293: while(Read_Keydoard() == EOF) /* ждать ответа */ 294: ; 295: } 296: return ch; 297: } 298: 299: kbd_wait() /* ждать ввода ..... */ 300: {int c; 301: while((c=kb_file()) == (-1)) 302: ; 303: return c&255; 304: } 305: 306: kb_file() /* ввода с клавиатуры или файла */ 307: { int c; 308: if (in_file) /* использование сценария */ 309: { c=Want_To_Abort(); /* вначале используется как флаг */ 310: if (waitchr && !c) 311: c=(-1); /* затем как символ */ 312: else 313: if (c || (c=getc(in_file)) ==EOF || c == 26) 314: { fclose(in_file); 315: cputs("\r\nScript File Closed\r\n"); 316: in_file=NULL; 317: waitchr=0; 318: c=(-1); 319: } 320: else 321: if (c == '\n') /* игнорировать в файле символы LF */ 322: c=(-1); 323: if (c == '\\') /* обработать ESC-последовательность */ 324: с= esc(); 325: if (vflag && c!=(-1)) /* верифицировать символ из файла */ 326: { putchx('['); 327: putchx((char)c); 328: putchx(']'); 329: } 330: } 331: else /* использование консоли */ 332: c=Read_Keyboard(); /* без использования файла */ 333: return(c); 334: } 335: 336: esc() /* перевод управляющих */ 337: {int c; 338: c=getc(in_file); /* символов из файла сценария */ 339: switch(toupper(c)) 340: { 341: case 'E': 342: c=ESC; 343: break; 344: 345: case 'N': 346: c='\n'; 347: break; 348: 349: case 'R': 350: c='\r'; 351: break; 352: 353: case 'T': 354: c='\t'; 355: break; 356: 357: case '^': 358: c=getc(in_file)&31; 359: break; 360: } 361: return(c); 362: } 363: 364: FILE *getfil() 365: { char fnm[20]; 366: getnam(fnm,15); /* получить имя файла */ 367: if (!strchr(fnm,'.'))) 368: strcat(fnm,".SCR"); 369: return(in_file=fopen(fnm,"r")); 370:} 371: 372: void getnam(b,s) char *b; /*поместить введенные данные в буфер*/ 373: int s; 374: {while (s-->0) 375: { if ((*b=(char kbd_wait())!='\r') 376: putchx(*b++); 377: else 378: break; 379: } 370: putchx('\n'); 381: *b=0; 382: } 383: 384: char *addext(b,e) /* добавить расширение по умолчанию */ 385: char *b, 386: *e; 387: { static char bfr[20]; 388: if (strchr(b,'.')) 389: return(b); 390: strcpy(bfr,b); 391: strcat(bfr,e); 392: return(bfr); 393: } 394: 398: void put_cap(c) char c; 398: { if (cap_file && c!= 13) /* снятие символов CR */ 398: fputc(c,cap_file); /* использовать буферизацию MS DOS */ 398: } 399: 400: void cap_flush() 401: { if (cap_file) 402: { fclose(cap_file) 403: cap_file=NULL; 404: cputs("\r\nCApture file closed\r\n"); 405: } 406:} 407: 408: /* Поддержка времени IBM PC (MS DOS)*/ 409: static long timr; /* регистр тайм-аута */ 410: 411: static union REGS rgv; 412: 413: long getmr() 414: { long now; /* время от полуночи*/ 415: rgv.x.ax=0x2C00; 416: intdos(&rgv,&rgv); 417: now = rgv.h.ch; /* часы */ 418: now *= 60L; /* в минуты */ 419: now += rgv.h.cl; /* плюс минуты */ 420: now *= 60L; /* в секунды */ 421: now += rgv.h.dh; /* плюс секунды */ 422: now *= 100L; 423: now += rgv.h.dl; 424: return(10L*now); /* вернуть время в милисекундах */ 425: } 426: 427: void delay(n) int n; /* задержка на n милисекунд*/ 428: {long wakeup; 429: wakeup=getmr() +(long)n /*установить время активации */ 430: while (getmr() timr); 440: } 441: 442: Set_Vid() 443: { _i_v(); /* Инициализация видео */ 444: return 0; 445: } 446: 447: void locate(row,col) int row, 448: col; 449: { _cy=row%25; 450: _cx=col%80; 451: _wrpos(row,col); /* использовать ML из CH2.ASM */ 452: } 453: 454: void deol() 455: {_deol(); 456: } 457: 458: void deos(); 459: {deol(); 460: if (_cy<24) /* если не последний */ 461: { rgv.x.ax=0x0600; 462: rgv.x.bx=(_atr<<8); 463: rgv.x.cx=(_cy+1)<<8; 464: rgv.x.dx=0x184F; 465: int86(0x10,&rgv,&rgv); 466: } 467: locate (_cy,_cx); 468: } 469: 470: void cls() 471: { _cls(); 472: } 473: 474:void cursor(yn) int yn; 475:{ rgv.x.cx=yn?0x0607 :0x2607; 476: rgv.x.ax=0x0100; 477: int86(0x10,&rgv,&rgv); 478: } 479: 480: void revvid(yn) int yn; 481:{ if (yn) 482: _atr=_color(8,7); /* черное на белом */ 483: else 484: _atr=_color(15,0); /* белое на черном*/ 485: } 486: 487: putchx(c) char c; /* вывести символ на экран */ 488: { if (c== '\n') 489: putch('\r'); 490: putch(c); 491: return c; 492: } 493: 494:Read_Keyboard() /* Читать символ с клавиатуры */ 495: /* вернуть 1, если буфер клавиатуры пуст */ 496:{int c; 497:if (kbhit()) 498: return(getch()); 499: return(EOF); 500: } 501: 502: /* ПОДДЕРЖКА МОДЕМА */ 503: static char mparm, 504: wrk[80]; 505: 506:void Init_comm() /* начальная установка порта */ 507: { static int ft=0; 508: if(ft++ == 0) 509: i_m(); 510: Set_Parity(1); 511: Set_Baud(1200); /* уровень передачи - 1200 бод */ 512: } 513: 514:#define B1200 0x80 /* Коды уровня передачи данных */ 515:#define B300 0x40 516: 517: Set_BAud(n) int n; /* n - уровень передачи */ 518: { if (n==300) 519: mparm = (mparm&0x1F)+B300; 520: else 521: if (n==1200) 522: mparm = (mparm&0x1F)+B1200; 523: else 524: return 0; /* неправильный уровень */ 525: sprintf(wrk,"BAud rate=%d\r\n",n); 526: cputs(wrk); 527: set_mdm(mparm); 528: return n; 529: } 530: 531:#define PAREVN 0x18 532:#define PARODD 0x10 533:#define PAROFF 0x00 534:#define STOP2 0x40 535:#define WORD8 0x03 536:#define WORD7 0x02 537:#define WORD6 0x01 538: 539: Set_Parity(n) int n; /* n - код паритета */ 540: { static int mmode; 541: if (n==1) 542: mmode(WORD8 | PAROFF); /* выключить*/ 543: else 544: if (n==2) 545: mmode(WORD7 | PAREVN); /* включить на четность */ 546: else 547: if (n==3) 548: mmode(WORD7 | PARODD); /* включить на нечетность*/ 549: else 550: return 0; /* неправильный код */ 551: mparm=(mparm&0xE0)+mmode; 552: sprintf(wrk,"Parity is %s\r\n",(n==1 ? "OFF": 553: (n==2 ? "EVEN" : "ODD")); 554: cputs(wrk); 555: set_mdm(mparm); 556: return n; 557: } 558: 559: Write_Modem (c) char c; /* вернуть 1,если O'K, иначе 0*/ 560: { wrtmdm(c); 561: return(1); 562: } 563: 564: Read_Modem() 565: { return(rdmdm()); 566: } 567: 568: Void Term_Comm() /* Разинсталлировать драйверы порта */ 569: { u_m(); 570: } 571: 572: /* Конец модуля cterm.c */ Рис.6-8. CTERM.C В CTERM демонстрируются возможности присоединения файлов, некото- рый простой, но эффективный язык описания сценария работы и некоторое количество "заглушенных" действий для возможных дальнейших расширений (таких, как эмуляция работы с терминалами типа VT52 и VT100). Имена файла сценария и присоединенного файла (capture file) могут быть переданы программе CTERM с помощью командной строки. Если команд- ная строка не содержит расширений для этих файлов, то по умолчанию для файла сценария устанавливается расширение SCR, а для присоединенного файла - CAP. Если же расширения в командной строке заданы явно, то они все равно заменяются на значения по умолчанию. Присоединенный файл ис- пользуется только в том случае, если он задан в командной строке. В то же время файл сценария может быть вызван в любой момент с помощью за- дания Esc-последовательности и таким же образом из одного файла сцена- рия может быть вызван другой. Список функций, входящих в CTERM, и их краткие описания приведены в таблице 6-13. Таблица 6-13 Функции CTERM.C ___________________________________________________________________ | | | | | Строка | Имя функции | Описание | |_________|________________|________________________________________| | | | | | 1 - 5 | | Паспорт программы | | 7 - 11 | | Файлы, подключаемые оператором #include| | 12- 20 | | Дефиниции | | 22- 43 | | Область глобальных переменных | | 45 | | Описание внешних переменных | | 47- 49 |wants_to_abort()| Контроль сигналов Ctrl-Break и Ctrl-C | | 52-165 | main() | Основной цикл: включает эмулятор модема| | | |и машины последовательных состояний, де-| | | |кодирующие команды, поступающие с уда-| | | |ленных устройств | | 167-297 | docmd() | Чтение, интерпретация и выполнение ло- | | | |кальных команд (поступают либо с консо-| | | |ли, либо из файла сценария) | | 299-304 | kbd_wait() | Ожидание поступления данных с консоли | | | |или файла сценария | | 306-334 | kb_file() | Чтение ключевых строк с консоли или | | | |файла сценария; если больше символов | | | |нет, то возвращает EOF | | 336-362 | esc() | Трансляция Esc-последовательности управ| | | |ления сценарием | | 364-370 | getfil () | Получение имени файла сценария и откры-| | | |тие этого файла | | 372-382 | getnam () | Получение строк с консоли или файла | | | |сценария с дальнейшим сохранением их в | | | |указанном буфере | | 384-393 | addext() | Если в указанном буфере имя файла зада-| | | |но без расширения, то добавить к нему | | | |указанное расширение | | 395-398 | put_cap() | Занести символ в присоединенный файл | | 400-406 | cap_flush() | Закрыть присоединенный файл и выключить| | | |режим сохранения | | 408-411 | | Данные таймера | | 413-425 | getmr() | Вернуть время в мсек, начиная с начала | | | |суток | | 427-432 | Delay() | Реализовать задержку на n мсек | | 434-436 | Start_Timer() | Установить таймер на n мсек | | 438-440 | Timer_Expired()| Проверить тип таймерного устройства | | 442-445 | Set_Vid() | Инициализация видео данных | | 447-452 | locate() | Позиционировать курсор на экране | | 454-456 | deol() | Удаление символов от курсора до конца | | | |строки | | 458-468 | deos() | Очистить экран от курсора до конца | | | |экрана | | 470-472 | cls() | Очистить весь экран | | 474-478 | cursor() | Сделать курсор видимым/невидимым | | 480-485 | revvid() | Переключить дисплей в нормальный/инвер-| | | |сный режим отображения | |_________|________________|________________________________________| Таблица 6-13 (продолжение) ___________________________________________________________________ | | | | | Строка | Имя функции | Описание | |_________|________________|________________________________________| | | | | | 487-492 | putchx() | Вывод символа на экран с использованием| | | |функции putch() (библиотека Microsoft C)| | 494-500 | read_keyboard()| Получение строки,введенной с клавиатуры| | 502-504 | | Область данных модема | | 506-512 | init_comm() | Начальная установка регистра ISR и | | | |т.д., а также инициирование модема | | 514-515 | | Дефиниции уровней передачи данных | | | | | | 517-529 | Set_Baud() | Контроль заданного для устройства UART | | | |уровня передачи данных в bps | | 531-537 | | Дефиниции значений паритетов, длин слов| | | |при передаче данных через модем | | 539-557 | Set_Parity() | Установка режима паритета | | 559-562 | Write_Modem() | Послать символ к модему | | 564-566 | Read_Modem() | Прочитать символ из буфера ISR | | 568-570 | Term_Comm() | Деинстолляция ISR от операционной сис- | | | |темы и восстановление первоначальных | | | |значений векторов прерываний | |_________|________________|________________________________________| Для взаимодействия с консолью в программе CTERM используются фун- кции библиотеки Microsoft C, определение которых содержится в файле CONIO.H, дополненные функциями модуля CH2.ASM. При использовании дру- гого компилятора значительную часть кода программы CTERM необходимо будет отредактировать. В CTERM также используется файл описания типов функций CTERM.H, содержимое которого приведено на рис.6-9. /* CTERM.H - описание типов функций CTERM.C */ int Wants_To_Abort(void); void main(int,char **); int docmd(void); int kbd_wait(void); int kb_file(void); int esc(void); FILE *getfil (void); void getnam (char *,int); char *addext (char *,char *); void put_cap (char); void cap_flush(void); long getmr(void); void Delay(int); void Start_Timer(int); int Timer_Expired(void); int Set_Vid(void); void locate(int,int); void deol(void); void deos(void); void cls(void); void cursor(int); void revvid(int); int putchx(char); int Read_Keyboard(void); void Init_Comm(void); int Set_Baud(int); int Set_Parity(int); int Write_Modem (char); int Read_Modem (void); void Term_Comm (void); /* функции CH1.ASM - взаимодействие с модемом */ void i_m(void); void set_mdm(int); void wrtmdm(int); void Send_Byte(int); int rdmdm(void); void u_m(int); /* функции CH1A.ASM - обработка особых ситуаций */ void set_int(void); void rst_int(void); int broke(void); /* функции CH2.ASM - взаимодействие с терминалом */ void _i_v(void); int _wrpos(int,int); void _deol(void); void _cls(void); void _color(int,int); Рис.6-9. CTERM.C Выполнение программы начинается с программы main() (строка 52). Вначале осуществляется проверка наличия в командной строке имен файла сценария и присоединенного файла (строки 56-59). В случае их наличия происходит открытие соответствующих файлов. Затем происходит инсталли- рование программы обработки особых ситуаций (строка 60), инициализация программы работы с терминалом (строка 61), очищается экран дисплея (строка 62) и на него выводится приветствие программы CTERM (строки 63-64). Устанавливается последовательное устройство с уровнем передачи данных 1200 bps и без контроля паритета (строки 65-67), и после этого программа входит в свой основной цикл (строки 68-159). Функционально этот цикл такой же, как и в программе ENGINE, за исключением того, что он расширен для распознавания клавиши Esc клави- атуры, нажатие которой является сигналом начала локальной командной последовательности (строки 70-73). Кроме того, этот цикл включает ме- ханизм моделирования и обработки состояний (строки 80-153) для распоз- навания Esc-последовательностей, характерных для терминалов типа VT52 и VT100. Чтобы ввести локальную команду, необходимо вначале нажать клавишу Esc, а затем клавишу с буквой, соответствующей необходимой ко- манде. После этого необходимо нажать любую клавишу для продолжения ра- боты. Для получения списка доступных команд необходимо ввести с клави- атуры последовательность Esc-H. Функция kb_file(), вызов которой осуществляется в основном цикле - строка 69, позволяет осуществлять ввод либо из файла сценария, либо с клавиатуры. Если открыт файл сценария (строки 308-330), то он ис- пользуется для ввода до тех пор, пока не будет достигнут EOF или пока пользователь не введет с клавиатуры Ctrl-C для остановки ввода из фай- ла сценария. Во всех остальных случаях ввод осуществляется с клавиату- ры (строки 331-332). В случае задания файла сценария и команды V, вво- димые из файла команды отображаются на экране (строки 325-329). Для указания символа Esc в файле сценария используется символ '\'. Когда в файле сценария обнаруживается символ '\' (строка 323- 324), то следующий за ним символ переводится в соответствии со следующими правилами: __________________________________________________________________ | | Символ | Перевод | _____________|____________________________________________________| | | E или e | Переводится в символ Esc | N или n | Переводится в символ LF (перевод строки) | R или r | Переводится в символ Enter (перевод каретки CR) | T или t | Переводится в символ табуляции ( Tab) | ^ | Указывает на то, что следующий символ должен рас- | | сматриваться как комбинация Ctrl-<символ> | _____________|____________________________________________________| Все остальные символы,включая и второй символ '',не переводятся. Когда программа обнаруживает во входном потоке (либо с консоли, либо из файла сценария) символ Esc, то происходит обращение к функции decmd() (строки 167-297) для декодирования следующего за Esc символа в команду и выполнения соответствующих для этой команды действий. Значения командных символов и действия, которые они производят, следующие: ___________________________________________________________________ | | | | Командный | Действие | | символ | | |___________|_______________________________________________________| | | | | D | Задержка на 0-9 секунд. За командой может идти десяти-| | | чная цифра, указывающая длительность задержки | | E | Установить паритет по четности | | F | Установить уровень передачи данных в 1200 бод | | H | Вывести список значений команд | | N | Отменить паритет | | O | Установить паритет по нечетности | | Q | Выход, возврат к MS-DOS | | R | Сброс модема | | S | Установить уровень передачи данных в 300 бод | | U | Использовать файл сценария (CTERM запросит имя файла) | | V | Верификация входного файла с отображением каждого | | | входного символа на экране | | W | Ждать поступления символа, следующий входной символ | | | должен совпадать с указанным | |___________|_______________________________________________________| Для всех остальных символов, следующих за Esc, генерируется сооб- щение: "Неизвестная команда Х" (где Х - значение поступившего симво- ла), за которым следует информационное сообщение "Используйте команду Н для получения помощи". Если в качестве входного потока используется файл сценария без эхосопровождения (не задана команда V), то функция docmd() выполняется быстрее . В случае же использования консоли, каждая введенная команда сопровождается отображением ее на экране. Хотя приведенный список команд является подмножеством доступных в модуле CDVUTL возможностей, он вполне достаточен для создания полезных сценариев. Предшественник программы СTERM (DTH5.EXE), который включал средства передачи файлов в протоколе B-Protocol фирмы CompuServe, но не имел дополнительных команд, использовался, начиная с 1986 г., для автоматической загрузки файлов в CompuServe Information Service с по- мощью использования файла сценария. В сочетании с автонастраиваемым модемом программа DTH5 полностью управляла целой тразакцией, начиная от подключения и заканчивая отключением, без вмешательства человека. На рис.6-10 показаны все способы окончательного создания програм- мы CTERM. Компиляция: C:> MASM CH1; C:> MASM CH1A; C:> MASM CH2; C:> MSC CTERM; Редактирование внешних связей: C:>LINK CTERM+CH1+CH1A+CH2; Использование (без файлов): C:> CTERM или (файла сценария): C:>CTERM scripfile или C:>CTERM scripfile capturefile Рис.6-10 Джим Кайл, Чип Рабинович Глава 7. Управление файлами и записями Основная часть большинства прикладных программ состоит в чтении, обработке и записи данных, хранимых на магнитных дисках. Эти данные организуются в файлы, которые определяются именами файлов. В свою оче- редь файлы могут быть организованы путем их группирования в каталоги. Операционные системы обеспечивают прикладные программы средствами, позволяющими манипулировать файлами и каталогами без учета характерис- тик технических средств дисковых устройств. Таким образом, прикладная программа соприкасается с ними единственно в части, касающейся формы и содержания данных, оставляя детали размещения и поиска данных на диске операционной системе. Обслуживание дисковой памяти, обеспечиваемое операционной системой, может быть представлено как функции, обрабаты- вающие файлы, и функции, обрабатывающие записи. Функции, обрабатываю- щие файлы, оперируют с файлом целиком как с поименованным объектом, в то время как функции, обрабатывающие записи, обеспечивают доступ к данным, содержащимся внутри файлов. (В некоторых системах дополнитель- ный класс функций обработки каталогов позволяет программам иметь дело также с группой файлов.) В этой главе обсуждается обращение к функциям MS-DOS, которые позволяют прикладным программам создавать, открывать, закрывать, переименовывать, удалять файлы с диска, читать и записывать данные в файлы на диск и проверять или изменять информацию (такую как атрибуты файла, дату, время), связанную с именами файлов на диске в каталоге диска. См.:"Программирование в среде MS-DOS. Структура MS-DOS." 1. История развития Текущие версии MS-DOS предусматривают два перекрывающихся множес- тва обслуживания управлением файлами и записями: функции, базирующиеся на использовании системного идентификатора файла (file handle) и функ- ции, использующие блок управления файлом (FCB). Оба множества функций реализованы в прерывании 21H (Таб.7-4) См."Системные вызовы: Прерыва- ние 21H." Ранние версии MS-DOS использовали блок управления файлом для всех способов доступа к файлу и записям, так как СР-М, ведущая операционная система для 8-разрядных микрокомпьютеров, использовала блоки управле- ния файлом. Фирма Microsoft приняла решение обеспечить совместимость с СР-М и помочь программистам в переносе многих существующих программ из среды СР-М в 16-разрядную среду MS-DOS; следовательно, версии 1.х включали множество функций, которые были функциональным надмножеством функций СР-М. Однако, по мере развития персональных компьтеров, оказалось, что метод доступа через блок управления файлом не дает хороших результатов при увеличении объемов и ускорении работы внешних устройств. Поэтому в версии 2.0 MS-DOS вводятся функции управления на основе системного идентификатора, чтобы обеспечить метод доступа к файлу и записи, подобный тому, который реализован в операционных системах UNIX, XENIX. Эти функции являются более легкими и гибкими для исполь- зования, чем подобные им функции, базирующиеся на блоке управления файлом (FCB), и полностью поддерживают иерархическую (древовидную) структуру каталога. Функции управления по системному идентификатору позволяют также управлять посимвольным устройством, таким как консоль или принтер, чтобы можно было обращаться к ним, как к файлам. В версии 3.0 MS-DOS введены дополнительные функции управления по системному идентификатору, расширены некоторые из существующих функций управления для использования их в сетевой среде и улучшена диагностика ошибок для всех функций. Функции управления по системному идентифика- тору, характеризующиеяс значительно большей эффективностью, чем функ- ции, базирующиеся на FCB , следует использовать для всех новых прик- ладных задач. Поэтому они обсуждаются первыми в данной главе. Таблица 7-1. Вызов функции прерывания 21H для управления файлами и записями __________________________________________________________________ Операция : функции :_________________________________________ : управления по основанные на :системному идентификатору FCB _______________________:_________________________________________ создать файл 3СH 16H создать новый файл 5ВH создать рабочий файл 5АH открыть файл 3DH 0FH закрыть файл 3EH 10H удалить файл 41H 13H переименовать файл 56H 17H выполнить последовательное чтение 3FH 14H выполнить последовательную запись 40H 15H выполнить чтение произвольной записи 3FH 21H выполнить записывание произвольной записи 40H 22H выполнить чтение произвольного блока 27H выполнить записывание произвольного блока 28H установить адрес области ввода/вывода 1FH получить адрес области ввода/вывода 2FH проанализировать имя файла 29H установить указатель чтения/записи 42H установить номер произвольной записи 24H получить размер файла 42H 23H получить/установить атрибуты файла 43H получить/установить метку даты и времени 57H сдублировать системный идентификатор файла 45H переназначить системный идентификатор файла 46H __________________________________________________________ 2. Использование функций управления по системному идентификатору Первоначально связь между прикладной программой и данными, храни- мыми на диске, устанавливаются при помощи имени файла на диске в фор- ме: <устройство:> <путь>\<имя файла.расширение> где <устройство:> определяет диск, на котором размещается файл, <путь> определяет каталог на диске, в котором размещен файл, и имя <файла.расширение> идентифицируют собственно файл. Если <устройство:> и/или <путь> опускается, MS-DOS устанавливает устройство и текущий ка- талог по умолчанию. Примеры допустимых имен пути: c:\payroll\taxes.dat letters\memo.txt budget.dat Имена путей могут содержаться в программном коде как часть дан- ных. В общем случае, однако, они вводятся пользователем с клавиатуры либо как параметр командной строки, либо в ответе на подсказку прог- раммы. Если путь передается как параметр командной строки, прикладная программа должна выделить его из другой информации в командной строке. Таким образом, чтобы позволить программе установить различие между пу- тем и другими параметрами, когда они оба объединены командной строкой, другие параметры , такие как переключатели, обычно начинаются с симво- лов косая черта (/) или подчеркивание (_). В функциях управления по системному идентификатору, которые используют путь, предполагается, чтобы имя было представлено строкой ASCIIZ - то есть имя должно окан- чиваться нулевым (zero) байтом. Если путь жестко закодирован в прог- рамме, нулевой байт должен быть частью строки ASCIIZ. Если путь вво- дится с клавиатуры или из параметра командной строки, нулевой байт должен быть добавлен программой. См.ниже: "Открытие существующего фай- ла." Чтобы использовать файлы на диске, программа открывает или созда- ет файл, обращаясь к подходящей функции MS-DOS и указывая путь в виде строки ASCIIZ. MS-DOS проверяет путь на неправильные символы и , если операция открытия или создания файла прошла успешно, возвращает 16-разрядный системный идентификатор файла. Программа использует этот идентификатор для последующих операций с файлом, таких как чтение и записывание записи в файл. Общее число системных идентификаторов для одновременно открытых файлов ограничивается двумя способами. Во-пер- вых, в одном процессе может использоваться не более 20 системных иден- тификаторов. Первые пять системных идентификаторов всегда назначаются стандартным устройствам, соответствующим по умолчанию последовательным устройствам CON, AUX и PRN: системный обслуживает умолчание идентификатор 0 стандартный ввод клавиатура (CON) 1 стандартный вывод видео дисплей(CON) 2 стандартный вывод видео дисплей(CON) ошибок 3 стандартное вспомога- первый коммуникацион- тельное устройство ный порт (AUX) 4 стандартная печать первый парралельный порт печати (PRN) Поэтому обычно процесс использует только 15 системных идентифика- торов из первоначально распределенных 20, однако при необходимости 5 системных идентификаторов стандартных устройств могут быть переназна- чены на другие файлы и устройства или закрыты и вновь использованы. Вдобавок к ограничению на использование внутри процесса не более 20 системных идентификаторов существует еще ограничение системы. MS-DOS поддерживает внутреннюю таблицу которая хранит пути всех открытых фай- лов и устройств с системными идентификаторами файлов для всех активных текущих процессов. Таблица содержит такую информацию как указатель те- кущего файла для операции чтения/записи, время и дату последней записи в файл. Размер этой таблицы, которая устанавливается при первоначаль- ной загрузке MS-DOS в память, определяется системным ограничением на количество одновременно открытых файлов или устройств. Такое ограниче- ние по умолчанию составляет 8 файлов и устройств. Таким образом, сис- темное ограничение является более сильным. Чтобы увеличить размер внутренней таблицы, необходимо включить в файл CONFIG.SYS утверждение FILES=nnn. (Установки CONFIG.SYS эффективны в случае перевызова систе- мы или при перезагрузке). Максимальное значение для утвержедения FILES есть 99 в версиях 2.x MS.DOS и 255 в версиях 3.x. См.: "Использование команд: CONFIG.SYS : FILES". 2.1. Обработка ошибок функций управления по сис- темному идентификатору При успешном завершении функций, основанных на системном иденти- фикаторе, MS-DOS передает управление в вызывающую программу с очищен- ным флагом состояния. При неуспешном завершении функции MS-DOS уста- навливает флаг состояния и возвращает код ошибки в регистр AX. Прог- рамма проверяет флаг состояния после каждой операции и выполняет под- ходящее действие для встретившейся ошибки. В таблице 7.2 перечислены наиболее часто встречающиеся коды ошибок для файлов или записей вво- да-вывода (исключая операции для сети). Коды ошибок версии 3.0 и более поздних версий являются надмножес- твом кодов ошибок MS-DOS версии 2.0. См.: "Приложение В: Критические коды ошибок; Приложение С: Расширенные коды ошибок." Большинство диаг- ностических ошибок версии 3 относятся к операциям на сети, у которых вероятность возникновения ошибки программы больше, чем при однопользо- вательской системе. Программам, работающим в сетевой среде, необходимо предусмотреть особенности и проблемы выполнения в среде сети. Напри- мер, система может не выполнять контроль, если программа работает с разделяемыми файлами. Под управлением версий 3.x MS-DOS программа может использовать функцию 59H прерывания 21H (Получить расширенную информацию об ошибке) для получения более детальных сведений о причине ошибки после неуспеш- ного завершения функции управления по системному идентификатору. Ин- формация, возвращаемая функцией 59H, включает тип устройства, повлек- шего ошибку, и рекомендуемые действия по восстановлению. П_р_е_д_у_п_р_е_ж_д_е_н_и_е: выполнение многих операций ввода/вы- ___________________________________________________________ Код Ошибка ___________________________________________________________ 02 Файл не найден 03 Путь не найден 04 Слишком много открытых файлов 05 Доступ невозможен 06 Неверный системный идентификатор 11 Неверный формат 12 Неверный код доступа 013 Неверные данные 15 Неправильное имя дисковода 17 Нет такого устройства 18 Нет больше файлов ___________________________________________________________ вода над файлом и записью, обсуждаемых в этой статье, может закончить- ся с ошибкой из-за внешних аппаратных сбоев. Такие ошибки могут перех- ватываться программой, исключение составляют некоторые критические случаи. См:"Программирование в среде MS-DOS: Установка MS-DOS: Обра- ботка исключительных ситуаций." 2.2. Создание файла Для создания файлов в MS-DOS есть три основанных на идентификато- ре функции прерывания 21H. _____________________________________________________________ функция имя _____________________________________________________________ 3СH Создать файл и назначить ему идентификатор (версии 2.x и более поздние) 5АH Создать рабочий файл (версия 3.0 и более поздние) 5ВH Создать новый файла (версия 3.0 и более поздние) ____________________________________________________________ При вызове этих функций сегмент и смещение к пути (представленно- му как строка ASCIIZ) задаются в регистрах DS-DX, а назначаемые новому файлу атрибуты - в регистре CX. Ниже приводятся возможные атрибуты значений: ___________________________________________________________ код атрибут ___________________________________________________________ 00H Обычный файл 01H Файл только для чтения 02H Скрытый файл 04H Системный файл ___________________________________________________________ Файлы, обладающие более чем одним атрибутом, могут создаваться при указании комбинаций значений, указанных выше. Например, чтобы соэ- дать файл, имеющий атрибуты "только для чтения" и "системный файл", необходимо в регистр CX поместить значение 05H. Если файл создан успешно, MS-DOS возвращает системный идентифика- тор файла в регистре AX, который должен использоваться для последова- тельного доступа в новом файле и устанавливает указатель чтения-записи файла в начало файла. Если файл не создается, MS-DOS устанавливает флаг состояния отличным от нуля и возвращает код ошибки в регистре AX. Для версий 2.x MS-DOS функция 3CH является единственной функцей создания файла. Однако ее надо использовать осторожно, так как, если файл с определяемым именем уже существует, функция 3CH будет открывать его и усекать до нулевой длины, уничтожая предыдущее содержание файла. Подобной сложности можно избежать, проверяя существование файла с по- мощью операции его открытия до вызова операции создания. В среде версии 3.0 MS-DOS и более поздних версий более предпочти- тельной в большинстве случаев является функция 5BH , - она завершается неуспешно, если файл с таким именем уже существует. В сетевой среде эта функция может быть использована для реализации семафоров, позволя- ющих синхронизировать выполнение программ в различных узлах сети. Функция 5AH используется для создания рабочего файла, который га- рантированно имеет уникальное имя. Это очень важно в сетевой среде, где некоторым копиям программ, работающих в разных узлах, может быть доступен некоторый объем логического диска на сервере. Функции пересылается адрес буфера, который может содержать имя устройства и/или путь, определяющий размещение для создаваемого файла. MS-DOS генерирует имя для созданного файла, которое является последо- вательностью алфавитных символов, и возвращает программе целиком всю спецификацию пути в виде строки ASCIIZ в тот же буфер и, одновременно в регистре AX содержится содержится системный идентификатор функции. Программа должна сохранить имя файла, чтобы можно было удалить файл позднее, если это необходимо; файл, созданный функцией 5AH, не разру- шается при завершении программы. П_р_и_м_е_р: Создать файл с именем MEMO.TXT в каталоге LETTERS на дисководе С , используя функцию 3CH. Любой существующий файл с тем же именем усекается до нулевой длины и открывается. fname db 'c:\letters\memo.txt',0 ;путь к файлу fhandle dw ? ;системный идентификатор . . . . mov dz, seg fname ; mov ds, dx mov dx, offset fname xor cx, ex mov ah, ch int 21h jc enter mov fhandle,ax . . . П_р_и_м_е_р:создать рабочий файл, используя функцию 5AH, и раз- местить его в каталоге \temp на устройстве С . MS-DOS добавляет генери- руемое имя файла к пути в буфере, поименованном fname. Полученные спе- цификации файла можно использовать для удаления файла. fname db 'c:\temp' db 10 dup (0) fhandle dw ? . . . mov dx, seg fname ;DS:DX адрес mov ds, dx ;пути временного файла mov dx, offset fname xor cx, cx ;cx - нормальный атрибут mov ah, 5ah ;функция 5AH создает временный файл int 21h ;выход в MS-DOS jc error ;переход,если создание завершилось ошибочно mov fhandle,ax ;в прот.случае сохранить идентификатор файла . . . 2.3. Открытие существующего файла Функция 3DH (открытие файла с управлением по системному идентифи- катору) окрывает существующий обычный, системный или скрытый файл в текущем или заданном каталоге. При вызове функции 3DH программа должна передать указатель на путь, задаваемый строкой ASCIIZ, в регистре DS:DX и однобайтовый код доступа в регистре AL. Этот код доступа вклю- чает разрешение на чтение/запись, режим совместного использования (разделения) файла и флаг наследования свойств. Биты кода доступа ус- танавливаются следующим образом: ----------------------------------------------------- Биты Описания ----------------------------------------------------- 0-2 Разрешение чтения/ записи (версия 2.0 и выше) 3 Зарезервирован 4-6 Режим разделения файла (версия 3.0 и выше) 7 Флаг наследования свойств (версия 3.0 и выше) ----------------------------------------------------- Поле разрешений чтения/ записи кода доступа определяет, как файл будет использоваться, и может принимать следующие значения: ---------------------------------------------------- Биты 0-2 Описание ---------------------------------------------------- 000 Требуется разрешение чтения 001 Требуется разрешение записи 010 Требуется разрешение чтения и записи ---------------------------------------------------- Чтобы открытие произошло успешно, поле разрешений должно быть совместимо с байтом атрибутов файла в каталоге диска. Например, если программа пытается открыть существующий файл, у которого установлен атрибут "только для чтения", а поле разрешений в байте кода доступа установлено для записи или чтения и записи, функция открытия завершит- ся неуспешно, и в регистр AX будет возвращен код ошибки. Поле режима разделения в байте кода доступа важно в сетевой сре- де. Оно определяет, можно ли другим программам также открыть файл, и если да, то какие операции они могут выполнять. Ниже указаны возможные значения поля режима одновременного использования файла: ---------------------------------------------------------------------- Биты 4-6 Описание ---------------------------------------------------------------------- 000 Режим совместимости. Другие программы могут от- крыть файл и выполнять операции чтения или записи, пока в одном из процессов не будет определен ка- кой-нибудь вариант режима одновременного использо- вания, отличный от режима совместимости. 001 Отказать всем. Другие программы не могут от- крыть файл. 010 Отказать в записи. Другие программы не могут открыть файл в режиме совместимости или в режиме разрешения записи. 011 Отказать в чтении. Другие программы не могут открыть файл в режиме совместимости или в режиме разрешения чтения. 100 Запретить режим совместимости. Другие программы могут открыть файл и выполнять операции и чтения, и записи, но не могут открыть файл в режиме сов- местимости. ---------------------------------------------------------------------- При активной служебной программе разделения зфайлов (то есть дол- жна быть предварительно загружена SHARE.EXE) результат любой операции открытия зависит и от содержания поля разрешений и поля разделения файла в байте кода доступа, и от разрешений и запросов одновременного использования файла другими процессами, которые уже успешно открыли файл. Бит наследования свойств в байте кода доступа управляет наследо- ванием порожденным процессом системного идентификатора файла. Если бит наследования обнулен, порожденный процесс может использовать системный идентификатор наследуемого файла без выполнения собственной операции его открытия. Последующие операции, выполняемые порожденным процессом над унаследованными файлами, влияют на указатель файла, связанного с системным идентификатором родительского файла. Если же бит наследова- ния устанавливается, порожденный процесс не наследует системный иден- тификатор. Если файл открыт успешно, MS-DOS возвращает его системный иденти- фикатор в AX и устанавливает указатель чтения/записи файла в начало файла; если файл не открылся, MS-DOS устанавливает флаг состояния и возвращает код ошибки в AX. П_р_и_м_е_р: Копирование первого параметра из командного хвоста списка программы сегментного префикса программы (в PSP) в массив fname и добавление null символа из имени файла ASCIIZ. Попытка открыть файл в режиме совместимости одновременного использования и с доступом чте- ние/ запись. Если файл не существует еще, создание его и назначение ему нормальных атрибутов. cmdtail equ fname db fhandle dw . . . ;подготовить к копированию имя файла mov si,cmdtail ;DS:SI = остаток команды mov di,seg fname ;FS:DI = буфер-приемник mov es,di ;имя файла из остатка команды mov di,offset fname cld ;прежде всего безопасность lodsb ;проверить длину остатка команды or al,al jz error ;переход, если остаток пуст label1: ;пропустить ведущие пробелы lodsb ;получить следующий символ cmp al,20h ;это пробел? jz label1 ;да, пропусти его label2: cmp al,0dh ;поиск конца jz label3 ;выход, если найден return cmp al,20h jz label3 ;выход, если найден пробел stosb ;иначе копируй этот символ lodsb ;взять следующий символ jmp label2 label3: xor al,al ;сохранить конечный NULL stosb ;для создания строки ASCIIZ ;сейчас открыть файл mov dx,seg fname ;DS:DX адрес mov ds,dx ;путь файла mov dx,offset fname mov ax,3d02h ;Функция 3DH открывает файл int 21h ;выход в MS-DOS jnc label4 ;переход, если файл найден cmp ax,2 ;ошибка2, файл не найден jnz error ;переход, если другая ошибка ;иначе создание файла xor cx,cx ;CX = атрибуты обычного файла mov ah,3ch ;функция 3CH создает файл int 21h ;выход в MS-DOS jc error ;переход, если создание неуспешное label4: mov fhandle,ax ;сохранить идентификатор файла . . . 2.4. Закрытие файла Функция 3EH (закрыть файл) закрывает файл, созданный или открытый функцией управления по системному идентификатору файла. Программа дол- жна поместить системный идентификатор закрываемого файла в BX. Если для файла выполнена операция записи, MS-DOS модифицирует данные "вре- мя" и "размер" в элементе каталога файла. По закрытии полностью осво- бождаются внутренние буферы MS-DOS, связанные с файлом на диске и, ес- ли это необходимо, измененяется таблица размещения файлов (FAT) на диске. Хорошим тоном в программировании является закрытие файлов срезу после завершения их обработки. Это особенно важно в случае изменения размеров файла, чтобы исключить потерю данных, при разрушении системы разрушается или неожиданном ее выключении пользователем. Метод модифи- кации FAT без закрытия файлов описан ниже в разделе "Функции дублиро- ваниея и переназначения". 2.5. Функции управления чтением и записью по иден- тификатору Функция 3FH (чтение файла или устройства) дает возможность прог- рамме читать данные из файла или устройства, открытого при помощи уп- равления по системному идентификатору. Перед вызовом функции 3FH прог- рамма должна установить регистры DS:DX на начало буфера данных, доста- точно большого, чтобы поместить перемещаемые данные, загрузить систем- ный идентификатор файла в BX и установить число считываемых байтов в CX. Запрашиваемая длина не может превышать 65535 байт. Программа, зап- рашивающая операцию чтения, отвечает за обеспечение буфера данных. Если операция чтения завершается успешно, данные читаются в выде- ленную память, начиная с текущей позиции указателя чтения/ записи фай- ла. MS-DOS затем увеличивает внутренний указатель чтения/записи для файла на длину пересланных данных и возвращает в вызывающую программу длину в регистре AX с очищенным флагом состояния. Единственным призна- ком достижения конца является возникновение ситуации, когда возвращае- мая длина меньше, чем запрашиваемая. Напротив, при использовании функ- ции 3FH для чтения из символьного устройства не в "прозрачном" режиме, чтение будет закончено или при достижении запрашиваемой длины или по- лучении сигнала "возврате каретки", в зависимости от того, какое собы- тие произойдет раньше. См.: "ПРОГРАММИРОВАНИЕ В СРЕДЕ MS-DOS: Програм- мирование для MS-DOS: Устройства символьного ввода-вывода". Если опе- рация чтения прерывается, MS-DOS возвращает взведенный флаг состояния и код ошибки в регистре AX. Функция 40H (запись в файл или устройство) осуществляет запись из буфера в файл (или устройство), используя системный идентификатор, по- лученный ранее в результате операции открытия или создания. Перед вы- зовом функции 40H программа должна установить DS:DX на начало буфера, содержащего исходные данные, системный идентификатор файла - в BX, а количество байтов для записи - в CX. Число байтов для записи не должно превышать 65535. Если операция записи завершилась успешно, MS-DOS по- мещает число записанных байт в AX, и увеличивает указатель чтения/ за- писи на эту величину; если операция записи заканчивается неудачно, MS-DOS устанавливает флаг состояния и возвращает код ошибки в AX. За- писи, меньшие чем один сектор (512 байт), не записываются прямо на диск. MS-DOS хранит записи во внутреннем буфере и записывает их на диск в случаях, когда внутренний буфер полон, когда файл закрывается или когда вызывается функция ODH прерывания 21H (переустановка диска). З_а_м_е_ч_а_н_и_е: Если осуществляется запись на диск, и диск по- лон, то вызывающая программа может идентифицировать это по возвращае- мой длине в AX, не равной запрашиваемой длине в CX. Признак Disk full (Диск полон) не возвращается как ошибка с установкой флага состояния. Особое использование функции записи заключается в усечении или расширении файла. Если вызывается функция 40H с длиной записи ноль в CX, размер файла согласовывается с текущим положением указателя чте- ния/ записи файла. П_р_и_м_е_р. Открыть файл MYFILE.DAT, создать файл MYFILE.BAK, скопировать содержимое .DAT файла в .BAK файл, используя 512-байтные блоки для чтения и записи, и затем закрыть оба файла. file1 db `MYFILE.DAT`,0 file2 db `MYFILE.BAK`,0 handle1 dw ? ;системный идентификатор MYFILE.DAT handle2 dw ? ;системный идентификатор MYFILE.BAK buff db 512 dup (?) ;буфер файла ввода/вывода . . . ;открыть MYFILE.DAT... mov dx,seg file1 ;DS:DX = адрес fname mov ds,dx mov dx,offset file1 mov ах,3d00h ;Функция 3DH = открыть (только ; для чтения) int 21h ;выход в MS-DOS jc error ;переход, если открытие ; прошло неуспешно mov handle1,ax ;сохранить системный идентификатор ;создать MYFILE.BAK... mov dx,offset file2 ;DS:DX = адрес fname2 mov cx,0 ;CX = системный идентификатор mov ah,3ch ;Функция 3CH int 21h ;выход в MS-DOS jc error ;переход, если создание ; прошло неуспешно mov handle2,ax ;сохранить системный идентификатор ; файла loop: ;читать MYFILE.DAT mov dx,offset buff ;DS:DX = адрес буфера mov cx,512 ;CX = длина чтения mov bx,handle1 ;BX = идентификатор MYFILE.DAT mov ah,3fh ;Функция 3FH int 21h ;выход в MS-DOS jc error ;переход, если чтение прошло ; неуспешно or ax,ax ;были ли прочитаны какие либо ; байты? jz done ;нет, достигнут конец файла ;записать MYFILE.BAK mov dx,offset buff ;DS:DX = адрес буфера mov cx,ax ;CX = длина записи mov bx,handle2 ;BX = системный идентификатор ; MYFILE.BAK mov ah,40h ;Функция 40h = писать int 21h ;выход в MS-DOS jc error ;переход, если запись прошла ; неуспешно cmp ax,cx ;запись завершена jne error ;переход, если диск полон jmp loop ;продолжить до конца файла done: ;сейчас закрыть файлы... mov bx,handle1 ;системный идентификатор MYFILE.DAT mov ah,3eh ;Функция 3EH = закрыть файл int 21h ;выход в MS-DOS jc error ;переход, если закрытие ; прошло неуспешно mov bx,handle2 ;системный идентификатор MYFILE.BAK mov ah,3eh ;Функция 3EH = закрыть файл int 21h ;выход в MS-DOS jc error ;переход, если закрытие ; прошло неуспешно . . . 2.6. Позиционирование указателя чтения/записи Функция 42H (передвинуть указатель файла) устанавливает положение указателя чтения/записи, связанного с данным системным идентификато- ром. Функция вызывается со знаковым 32-битовым смещением в регистрах CX и DX (наиболее значащая часть в CX), системным идентификатором фай- ла в BX и режимом позиционирования в AL: ---------------------------------------------------------- Режим Значения ---------------------------------------------------------- 00 Установка смещения относительно начала файла 01 Установка смещения относительно текущей позиции указателя чтения/записи 02 Установка смещения относительно конца файла ---------------------------------------------------------- Если функция 42H завершилась успешно, MS-DOS возвращает результат абсолютного смещения (в байтах) указателя файла относительно начала файла в регистрах DX и AX с наиболее значащей частью в DX; если функ- ция заканчивается ошибочно, MS-DOS устанавливает флаг состояния и воз- вращает код ошибки в AX. Таким образом, программа может получить раз- мер файла, вызывая функцию 42H с нулевым смещением и режимом позицио- нирования 2. Функция возвращает значение в DX:AX, которое представляет смещение позиции конца файла относительно начала файла. П_р_и_м_е_р: Допустим, что файл MYFILE.DAT ранее открыт и его системный идентификатор хранится в переменной fhandle. Позиция указа- теля файла размещается в 32768 байте относительно начала файла, и за- тем осуществляется чтение 512 байт данных, начиная с этой позиции фай- ла. fhandle dw ? ; системный идентификатор, полученный ; по предыдущей операции открытия buff db 512 dup (?) ; буфер данных файла . . . ; позиция указателя файла... mov cx,0 ; CX = старшая часть смещения файла mov dx,32768 ; DX = младшая часть смещения файла mov bx,fhandle ; BX = идентификатор файла mov al,0 ; AL = режим позиционирования mov ah,42h ; Функция 42H = позиционирование int 21h ; выход в MS-DOS jc error ; переход,если функция завершилась ; неуспешно ; сейчас читать 512 байт из файла mov dx,offset buff; DS:DX = адрес буфера mov cx,512 ; CX = длина 512 байт mov bx,fhandle ; BX = идентификатор файла mov ah,3fh ; Функция 3FH int 21h ; выход в MS-DOS jc error ; переход, если функция завершилась ; неуспешно cmp ax,512 ; байты прочитаны? jne error ; переход, при частичном прочтении . . П_р_и_м_е_р: Допустим, что файл MYFILE.DAT ранее открыт и его системный идентификатор сохраняется в переменной fhandle. Найти размер файла в байтах, размещая указатель файла в нуль байтов относительно конца файла. Возвращаемое смещение относительно начала файла, есть размер файла. fhandle dw ? . . . ;позиционировать указатель файла ;в конец файла mov cx,0 ;CX = старшая часть смещения mov dx,0 ;DX = младшая часть смещения mov bx,fhandle ;BX = идентификатор файла mov al,2 ;AL = режим позиционирования mov ah,42h ;функция 42H = позиционирование int 21h ;передать управление MS-DOS jc error ;переход в случае ошибки ;в случае успеха, DX:AX ;содержит размер файла . . . 2.7. Другие операции управления файлом по иденти- фикатору MS-DOS обеспечивает другие функции ориентированные на управление файлом по системному идентификатору: - переименование (или перемеще- ние) файла, удаление файла, чтение или изменение атрибутов файла, чте- ние или изменение даты и времени создания файла и дублирование или пе- реназначение файла. Первые три из них используют строку ASCIIZ, чтобы определить файл, однако не возврaщают системный идентификатор файла. 2.7.1. Переименование файла Функция 56H (переименование файла) переименовывает существующий файл и/или перемещает файл, размещенный в одной иерархической структу- ре в другую. Файл, который будет переименовываться, не может быть скрытым или системным файлом или подкаталогом и не должен быть откры- тым в текущий момент каким-либо процессом; попытки переименовать отк- рытый файл могут исказить информацию на диске. MS-DOS переименовывает файл, изменяя элемент каталога, и перемешает файл, удаляя элемент те- кущего каталога и создавая новый элемент в заданном каталоге с тем же именем файла. Физическое размещение данных файла на диске не изменяет- ся. И текущее, и новое имя файла должно быть строкой ASCIIZ и может включать имя дисковода и определение пути; символы * и ? не допускают- ся для использования в имени файла. Программа вызывает функцию 56H с адресом текущего пути в регистрах DS:DX и адресом нового пути в ES:DI. Если элементы пути в двух строках не совпадают и оба пути правильны, файл перемещается из исходного каталога в целевой каталог. Если пути соответствуют, но имена файлов различны, MS-DOS просто модифицирует элемент каталога, записывая новое имя. Если функция выполняется успешно, MS-DOS возвращает управление в вызываемую программу с очищенным флагом состояния. Функция заканчива- ется с ошибкой, если новое имя уже существует в целевом каталоге; в этом случае MS-DOS устанавливает флаг состояния и возвращает код ошиб- ки в AX. П_р_и_м_е_р: Изменить имя файла MYFILE.DAT в MYFILE.OLD. В этой же операции переместить файл из каталога \WORK в каталог \BACKUP. file1 db `\WORK\MYFILE.DAT`,0 file2 db `\BACKUP\MYFILE.OLD`,0 . . . mov dx,seg file1 ; DS:DX = старое fname mov ds,dx mov es,dx mov dx,offset file1 mov di,offset file2 ; ES:DI = новое fname mov ah,56h ; Функция 56H = переименовать int 21h ; выход в MS-DOS jc error ; переход, если переименование ; завершилось неуспешно . . . 2.7.2. Удаление файла Функция 41H (Удаление файла) эффективно удаляет файл с диска. Пе- ред вызовом функции программа должна установить регистры DS:DX, чтобы указать в ASCIIZ путь к файлу, который нужно удалить. Указанный путь не может быть подкаталогом или файлом только для чтения, и файл не должен быть открыт каким-либо процессом. Если операция прошла успешно, MS-DOS удаляет файл, просто помечая первый байт его элемента каталога специальным символом (0E5H), делая элемент каталога нераспознаваемым. MS-DOS далее модифицирует FAT на диске так, что кластеры, ранее принадлежавшие файлу, становятся "сво- бодными", и возвращает управление в программу с очищенным флагом сос- тояния. Если функция удаления завершилась неудачно, MS-DOS устанавли- вает флаг переноса и возвращает код ошибки в регистре AX. Физическое содержание кластеров, назначенных файлу, не изменяется при выполнении операции удаления, таким образом по причинам безопас- ности секретной информации она должна быть перекрыта пробелами или не- которыми другими символами перед тем, как удалить файл с помощью функ- ции 41H. П_р_и_м_е_р: Удалить файл MYFILE.DAT, расположенный в каталоге \WORK на дисководе C. fname db `C:\WORK\MYFILE.DAT`,0 . . . mov dx,seg fname ; DS:DX = адрес fname mov ds,dx mov dx,offset fname mov ah,41h ; Функция 41H = удалить int 21h ; выход в MS-DOS jc error ; переход, если удаление ; завершилось неуспешно . . . 2.7.3. Получение/установка атрибутов файла Функция 43H (Получить/установить атрибуты файла) получает или мо- дифицирует атрибуты существующего файла. Перед вызовом функции 43H программа должна установить регистры DS:DX, чтобы указать путь в виде строки ASCIIZ для файла. Чтобы прочитать атрибуты, программа должна установить нуль в AL; чтобы установить атрибуты, она должна установить 1 в AL и разместить код атрибута в CX. См. выше: "Создание файла." Если функция выполнена успешно, MS-DOS читает или устанавливает байт атрибута в элементе каталога файла и возвращает управление с об- нуленным флагом состояния и атрибутом файла в CX. Если функция завер- шается ошибочно, MS-DOS устанавливает флаг состояния и возвращает код ошибки в AX. Функция 43H не может использоваться для установки бита метки тома (3-й бит) или бита подкаталога (4-й бит) файла. Ее также не следует использовать для файла, открытого каким-либо процессом. П_р_и_м_е_р: Изменить атрибуты файла MYFILE.DAT в каталоге \BACKUP на дисководе C на "только для чтения". Это защищает файл от случайного удаления с диска. fname db `C:\BACKUP\MYFILE.DAT`,0 . . . mov dx,seg fname ;DX:DS = адрес fname mov ds,dx mov dx,offset fname mov сx,1 ;CX = атрибут (только для чтения) mov al,1 ; AL = режим(0 =взять 1 =установить) mov ah,43h ;Функция 43H = взять/установить ; атрибут int 21h ;выход в MS-DOS jc error ;переход, если установка атрибутов ; завершилась неуспешно . . . 2.7.4. Получение/установка даты и времени создания файла Функция 57H (Получить/установить дату/время создания файла) чита- ет или устанавливает время и дату создания каталога для открытого фай- ла. Чтобы установить время и дату, программа должна вызвать функцию 57H с требуемым именем в CX и требуемой датой в DX, идентификатора файла (получает из ранее открытого файла или из операции создания) в BX и значения 1 в AL. Чтобы прочитать дату и время, функция вызывается с AL, содержащим 0, и системным идентификатором файла в BX; время воз- вращается в регистр CX, а дата - в DX. Как и для других функций, ори- ентированных на управление по системному идентификатору, если функция завершена успешно, флаг состояния возвращается обнуленным; если функ- ция прерывается из-за ошибки, MS-DOS возвращает установленный флаг состояния и код ошибки в AX. Форматы, используемые для времени и даты файла, те же, что и для элементов каталога диска и FCB. См.ниже: "Структура Блока управления файлом." Главное использование функции 57H заключается в том, чтобы изме- нить поле времени и даты, если файл не изменялся, и предохранить от изменения, если файл изменяется. В последнем случае программа может использовать эту функцию с AL=0, чтобы получить предыдущую метку даты и времени файла, модифицировать файл и затем восстановить начальную дату и время файла, повторно вызывая функцию с AL=1 перед закрытием файла. 2.7.5. Функции дублирования и переназначения Обычно FAT диска и каталога не изменяются до закрытия файла, даже если файл изменяется. Таким образом, когда файл закрывается, некоторые новые данные, добавленные к файлу, могут быть потеряны при сбое систе- мы или неожиданном выключении. Очевидная защита против такой потери заключается в простом закрытии и открытии файла каждый раз, когда файл изменяется. Однако, это относительно медленная процедура и в сетевой среде может привести к тому, что программа потеряет управление файлом для другого процесса. Используя идентификатор второго файла, созданного при помощи фун- кции 45H (дублирование идентификатора файла), чтобы дублировать перво- начальный системный идентификатор модифицируемого файла можно защитить данные, добавленные к файлу на диске, до закрытия файла. Чтобы исполь- зовать функцию 45H, программа должна установить дублируемый системный идентификатор в BX. Если операция завершается успешно, MS-DOS очищает флаг состояния и возвращает новый системный идентификатор в AX. Если функция выполнена успешно, системный идентификатор дублиро- вания может быть закрыт обычным способом функцией 3EH. Это приводит к требуемому изменению каталога диска и FAT. Начальный идентификатор ос- тается открытым и программа может продолжать использовать его для опе- рации чтения и записи. З_а_м_е_ч_а_н_и_е: Пока открыт идентификатор-дубль, перемещение указателя чтения/записи любого идентификатора перемещает указатель связанного с ним другого идентификатора. П_р_и_м_е_р: Допустим, что файл MYFILE.DAT открыт ранее и его идентификатор сохранен в переменной fhandle. Дублируем идентификатор, и затем закрываем дублирование, чтобы любые данные, только что запи- санные в файл, сохранялись на диске и чтобы соответственно изменялся элемент каталога для файла. fhandle dw ? ;системный идентификатор, полученный . ;по предыдущей операции открытия . . ;продублировать идентификатор mov bx,fhandle ;BX = идентификатор файла mov ah,45h ;функция 45H = идентификатор-дубль int 21h ;передать управление MS-DOS jc error ;переход в случае ошибки ;теперь закрыть новый идентификатор... mov bx,ax ;BX = идентификатор-дубль mov ah,3eh ;функция 3EH = закрыть int 21h ;передать управление MS-DOS jc error ;переход в случае ошибки mov bx,fhandle ;заменить закрытый идентификатор . ; активным . . Функция 45H иногда используется совместно с функцией 46H (Создать идентификатор-дубль). Функция 46H устанавливает некоторый идентифика- тор в качестве идентификатора-дубля другого открытого файла для ис- пользования в операциях чтения/записи. Так переназначается идентифика- тор. Наиболее общее использование функции 46H заключается в том, чтобы изменить значение стандартного ввода и стандартного вывода функций уп- равления по системному идентификатору перед загрузкой порожденного процесса EXEC-функцией. В этом случае ввод из порожденной программы может быть переназначен на файл, или вывод может быть переназначен в файл, без каких-либо особых знаний о порожденной программе. В подобных случаях функция 45H используется также, чтобы создать копии идентификатора стандартного ввода и стандартного вывода перед тем, как они будут переназначены, с тем чтобы их первоначальные значе- ния могли быть восстановлены после выхода из порожденной программы. См.: "ПРОГРАММИРОВАНИЕ В СРЕДЕ MS-DOS: Настройка в среде MS-DOS: Запи- сывание фильтров MS-DOS." 3. Использование функций, основанных на FCB Блок управления файлом - это структура данных, размещенная в об- ласти памяти прикладной программы, содержащая необходимую информацию об открытом файле на диске: имя дисковода, имя файла и расширение, указатель позиции в файле, и так далее. Каждый открытый файл должен иметь собственный FCB. Информация в FCB обрабатывается совместно и MS-DOS, и прикладной программой. MS-DOS перемещает данные из файла на диске, связанного с FCB, ис- пользуя буфер данных, называемый DTA (область передачи данных). Теку- щий адрес DTA управляется прикладной программой, хотя каждая программа имеет буфер размером 128 байт по умолчанию со смещением 80 в префиксе программного сегмента (PSP). См.: "ПРОГРАММИРОВАНИЕ В СРЕДЕ MS-DOS: Программирование для MS-DOS: Структура прикладной программы". В ранних версиях MS-DOS единственное ограничение на число однов- ременно открытых файлов определялось выделяемым объемом памяти для FCB и буферов данных в прикладных программах. Однако в версии 3.0 MS-DOS и более поздних версиях при использовании режима совместного использова- ния файлов, MS-DOS задает некоторые ограничения на использование FCB, чтобы упростить обработку сетевых связей файлов. Если прикладная прог- рамма пытается открыть слишком много FCB, MS-DOS просто закрывает не- давно используемые FCB, чтобы сохранить общее число в заданных преде- лах. Директива FCB в CONFIG.SYS файле позволяет пользователю устано- вить максимальное число FCB и определить некоторое число FCB, которые необходимо защитить против автоматического закрытия системой. По умол- чанию значение открытых одновременно используемых FCB равно четырем, а FCB, защищенных от автоматического закрытия системой, равно нулю. См.: "Команды пользователя: CONFIG.SYS: FCB". Так как операции FCB предшествовали версии 2.0 MS-DOS и так как FCB имели фиксированную структуру, в которой отсутствовало место для указания пути, функции FCB, обслуживающие файлы и записи, не поддержи- вают иерархическую структуру каталога. Многие основанные на FCB опера- ции могут быть выполнены только на файлах в текущем каталоге на диске. По этой причине в новых программах следует избегать использования ос- нованных на FCB операций над файлами и записями. 3.1. Структура блока управления файлом (FCB) Каждый FCB представляет собой 37-байтовый массив, размещенный в собственной области памяти прикладной программы; FCB содержит всю ин- формацию, необходимую чтобы определить файл на диске и доступ к данным внутри файла: идентификатор дисковода, имя файла, расширение, размер файла, размер записи, различные указатели файла и метки дат и времени. Структура FCB показана в таблице 7-3. Идентификатор дисковода: Инициализируется прикладной программой, чтобы определить дисковод, на котором должны открываться или созда- ваться файлы. 0 = дисковод по умолчанию, 1 = дисковод А, 2 = дисковод B и так далее. Если прикладная программа устанавливает в данном байте нуль (чтобы использовать дисковод по умолчанию), MS-DOS изменяет байт во время операции открытия или создания, чтобы установить действитель- но используемый дисковод. Этому дисководу будут всегда соответствовать Таблица 7-3. Структура обычного блока управления файлом FCB ----------------------------------------------------------------- Чем обраба- Смещение Размер Описание тывается (в байтах) (в байах) -------------------------------------------------------------- Программой 00H 1 Идентификатор дисковода Программой 01H 8 Имя файла Программой 09H 3 Расширение файла MS-DOS 0CH 2 Номер текущего блока Программой 0EH 2 Размер записи (в байтах) MS-DOS 10H 4 Размер файла (в байтах) MS-DOS 14H 2 Метка даты MS-DOS 16H 2 Метка времени MS-DOS 18H 8 Зарезервированы MS-DOS 20H 1 Номер текущей записи Программой 21H 4 Номер записи для случайной выборки -------------------------------------------------------------- значения 1 или выше. Имя файла: Стандартное восьмисимвольное имя файла; инициализиру- ется прикладной программой; должно быть выровнено на левую границу и дополнено пробелами, если длина имени менее 8 символов. Может исполь- зоваться имя устройства (например, PRN ), заметим, что двоеточие от- сутствует после имени устройства. Расширение файла: Трехсимвольное расширение файла; инициализиру- ется прикладной прграммой; должно быть выровнено на левую границу и дополнено пробелами, если расширение короче трех символов. Номер текущего блока: в момент открытия файла MS-DOS устанавлива- ет его в нуль. Номер блока и номер записи вместе определяют указатель записи в случае последовательного метода доступа. Размер записи: Размер записи (в байтах) используется программой. MS-DOS устанавливает это поле равным 128 в момент открытия или созда- ния файла; программа может впоследствии изменить поле для установления любого требуемого размера записи. Если размер записи больше, чем 128 байт, DTA в PSP не может использоваться по умолчанию, так как это бу- дет противоречить собственному коду и данным программы. Размер файла: Размер файла в байтах. MS-DOS устанавливает это по- ле, используя элемент каталога файла при открытии файла. По меньшей мере, первые 2 байта из четырехбайтного поля являются значащими байта- ми размера файла. Метка даты: Дата последней операции записи в файл. MS-DOS уста- навливает это поле из элемента каталога файла при открытии файла. Это поле использует тот же формат, что и основанная на идентификаторе фун- кция 57H (Получить/установить дату/время создания файла): Формат данных ¦ Биты: 15 14 13 12 11 10 9 8 ¦ 7 6 5 4 3 2 1 0 -------------------------+------------------------¬ Содержание:¦ Y Y Y Y Y Y Y M ¦ M M M D D D D D ¦ L------------------------+------------------------- -------------------------------------------------------------- Биты Содержание -------------------------------------------------------------- 0-4 День месяца (1-31) 5-8 Месяц (1-12) 9-15 Год (относительно 1980) -------------------------------------------------------------- Метка времени: Время последней операции записи в файл. MS-DOS инициализирует это поле из элемента каталога файла при открытии файла. Эта функция использует тот же формат, что и основанная на идентифика- торе функция 57H. Формат времени ¦ Биты: 15 14 13 12 11 10 9 8 ¦ 7 6 5 4 3 2 1 0 -------------------------+------------------------¬ Содержание:¦ H H H H H M M M ¦ M M M S S S S S ¦ L------------------------+------------------------- --------------------------------------------------------------- Биты Содержание --------------------------------------------------------------- 0-4 Число двухсекундных увеличений (0-29) 5-10 Минуты (0-59) 11-15 Часы (0-23) --------------------------------------------------------------- Номер текущей записи: Совместно с номером блока образует указа- тель записи, используемый в операциях последовательного чтения и запи- си. MS-DOS не инициализирует это поле при открытии файла. Номер записи ограничивается в пределах от 0 до 127; таким образом, в блоке содер- жится 128 записей.Начальной записью в файле является запись 0 блока 0. Указатель записи при прямой выборке: 4-байтное поле определяет запись, необходимую для передачи функциями произвольной выборки запи- сей 21H, 22H, 27H и 28H. Если размер записи 64 байта и более, то ис- пользуются только первые 3 байта этого поля. MS-DOS изменяет это поле после чтения и записи произвольного блока (функции 27H и 28H), а не после чтения записи с произвольным доступом (функции 21H и 22H). Расширенный FCB, который на 7 байт длиннее, чем обычный FCB, мо- жет быть использован для доступа к файлам с особенными атрибутами, та- кими как скрытый, cистемный, только для чтения. Эти 7 байт расширенно- го FCB просто добавляются в начало к обычному формату FCB (См. Табл.7-4). Первый байт расширенного FCB всегда содержит значение 0FFH, которое никогда не может быть кодом устройства и, следовательно, слу- жит сигналом MS-DOS, что используется как расширенный формат. Следую- щие пять байтов резервируются и должны быть нулевыми, и последний байт префикса определяет атрибуты файла, с которым мы работаем. Остальная часть расширенного FCB точно такая же, как и у обычного FCB. В общем, расширенный FCB может использоваться любой функцией MS-DOS, которая работает с обычным FCB. Таблица 7-4. Структура расширенного блока управления файла --------------------------------------------------------------- Чем обраба- Смещение Размер Описание тывается (в байтах) (в байтах) --------------------------------------------------------------- Программой 00H 1 Флаг расширенного FCB = 0FFH MS-DOS 01H 5 Зарезервированы Программой 06H 1 Байт атрибута файла Программой 07H 1 Идентификатор дисковода Программой 08H 8 Имя файла Программой 10H 3 Расширение файла MS-DOS 13H 2 Номер текущего блока Программой 15H 2 Размер записи (в байтах) MS-DOS 17H 4 Размер файла (в байтах) MS-DOS 1BH 2 Метка даты MS-DOS 1DH 2 Метка времени MS-DOS 1FH 8 Зарезервированы MS-DOS 27H 1 Номер текущей записи Программой 28H 4 Номер записи при прямом доступе --------------------------------------------------------------- Флаг расширенного FCB: Когда 0FFH представлено в первом байте FCB, это сигнал MS-DOS, что расширенный FCB (44 байта) используется вместо обычного FCB (37 байт). Байт атрибута файла: Должен инициализироваться прикладной прог- раммой, когда расширенный FCB используется для открытия или создания файла. Биты этого поля имеют следующие значения: --------------------------------------------------------------- Бит Значение --------------------------------------------------------------- 0 Только читать 1 Скрытый 2 Системный 3 Метка тома 4 Каталог 5 Архив 6 Зарезервирован 7 Зарезервирован --------------------------------------------------------------- 3.2. Функции, основанные на FCB,и PSP PSP содержит некоторые элементы данных , которые представляют ин- терес при использовании операций над файлами и записями FCB: два FCB, называемые FCB по умолчанию, DTA по умолчанию и хвост командной стро- ки. В следующей таблице показаны размер и размещение этих элементов. -------------------------------------------------------------- Смещение PSP Размер Описание (в байтах) (в байтах) -------------------------------------------------------------- 5CH 16 FCB = 1 по умолчанию 6CH 20 FCB = 2 по умолчанию 80H 1 длина хвоста командной строки 81H 127 текст командной строки 80H 128 DTA по умолчанию -------------------------------------------------------------- Когда MS-DOS загружает программу в память для выполнения, она ко- пирует хвост командной строки в PSP cо cмещением 81H, размещает длину хвоста командной строки в байтах со смещением 80H и анализирует первые два параметра в хвосте командной строки в FCB по умолчанию со смещени- ем PSP 5CH и 6CH. (Хвост командной строки состоит из командной строки, используемой для вызова программы, без собственно имени программы и символов переназначения или сквозного канала и связанных с ними имена- ми файлов или именами устройств). MS-DOS устанавливает начальный адрес DTA для программы в PSP: 0080H. По некоторым причинам FCB и DTA часто перемещаются в другое место внутри области памяти программы. Во-пер- вых, принимаемая по умолчанию DTA позволяет обрабатывать очень мало записей. Вдобавок ко всему, принимаемые по умолчанию FCB существенно перекрываются, и первый байт DTA по умолчанию и последний байт первой FCB конфликтуют. Если хвост команды или DTA не перенесены заранее, то первая операция с файлом или записью, использующая FCB, разрушает хвост командной строки. Функция 1AH (установить адрес DTA) используется, чтобы изменить адрес DTA. Она вызывается с сегментом и смещением нового буфера, кото- рый нужно использовать как DTA, в DS:DX. Адрес DTA остается тем же до другого вызова функции 1AH, независимо от других вызовов функций уп- равления файлами или записями; нет нужды переустанавливать адрес перед каждым чтением или записью. З_а_м_е_ч_а_н_и_е: Программа может использовать функцию 2FH (по- лучить адрес DTA), чтобы получить текущий адрес DTA перед ее изменени- ем, так что начальный адрес может быть восстановлен позже. 3.3. Синтаксический анализ имени файла Перед обращением к функциям открытия или создания файла имя уст- ройства, имя файла и расширение должно быть размещено в подходящих по- лях FCB. Имя файла может быть закодировано в программе или получено из хвоста командной строки в PSP или подсказки пользователя и чтения ее с помощью одной из нескольких функций работы с устройством символьного ввода. MS-DOS автоматически анализирует первые два параметра в хвосте командной строки программы в блоках управления файлами, заданными по умолчанию в PSP:005CH и PSP:00CH. Однако, попытка различить перекляча- тели и имена файлов не делается, так что преданализ FCB не является обязательно полезным для прикладной программы. Если бы именам файлов предшествовали какие-либо переклячатели, программа сама должна была бы извлекать имена файлов из хвоста командной строки. Таким образом, программа ответственна за определение того, какие из параметров явля- ются переключателями, а какие именами файлов, и где каждый параметр начинается и заканчивается. После размещения имени файла может использоваться функция 29H (Анализ имени файла), чтобы поверить его на неправильные символы и разделители и ввести эти компоненты в подходящие поля в FCB. Имя файла должно быть строкой в стандартном виде: ИМЯ ФАЙЛА.РАСШИРЕНИЕ. Допуска- ется использование в имени файла и/или в расширении шаблонных симво- лов: символ "звездочка" (*) заменяется на управляющий символ "знак вопроса" (?). Чтобы вызвать функцию 29H , регистры DS:SI должны указывать на подходящие имена файлов; ES:DI должны указывать на 37- байтный буфер, который станет FCB для файла, и AL должен содержать управляющий код вида анализа. См.: "Системные вызовы: Прерывание 21H, функция 29H". Если код дисковода не включается в имя файла, MS-DOS вставляет номер текущего дисковода в FCB. Анализ прекращается при первом символе завершения, появившемся в имени файла. В качестве символов завершения используются следующие символы: ; , = + / " [ ] : < > : пробел Если символ (:) находится в неподходящей позиции для отделения идентификатора дисковода или если символ (.) находится в неподходящем месте по ограничению расширения, такие символы также будут трактовать- ся как символы завершения. Например, имя файла C:MEMO.TXT будет проа- нализировано как правильное имя; однако ABC:DEF.DAY будет проанализи- ровано как ABC. Если определен неверный дисковод в имени файла, функция 29H возв- ратит 0FFH в AL; если имя файла содержит какой-либо шаблонный символ, то возвратит 1. В противном случае AL содержит 0, указывая на правиль- ность имени. Заметим, что эта функция просто анализирует имя файла в FCB. Она не инициализирует другие поля FCB (хотя она обнуляет поля длины теку- щего блока и размера записи) и не проверяет, существует ли в действи- тельности определяемый файл. 3.4. Обработка ошибок и функции, основанные на FCB Основанные на FCB функции обработки файлов и записей возвращают небольшую информацию об ошибках при неуспешном завершении функции. Обычно, функция FCB возвращает 0 в AL , если функция завершена успеш- но, и 0FFH, в противном случае. Под управлением версии 2.x MS-DOS программа должна сама определить причину ошибки. Под управлением вер- сии 3.x MS-DOS, однако, за вызовом неуспешно завершаемой функции может последовать вызов функции 59H прерывания 21H (получить расширенную ин- формацию об ошибке). Функция 59H возвратит те же описания кодов для ошибки, включая локализацию ошибки и предлагаемую стратегию устране- ния, как в случае ввода/вывода, основанного на системном идентификато- ре. 3.5. Создание файла Функция 16H (создание файла с FCB) создает новый файл и открывает его для операции последовательного чтения/записи. Функция вызывается с DS:DX, указывающим на правильный, неоткрытый FCB. MS-DOS ищет текущий каталог для указанного имени файла. Если имя файла найдено, MS-DOS ус- танавливает длину файла 0 и открывает файл, усекая его до файла нуле- вой длины; если имя файла не найдено, MS-DOS создает новый файл и отк- рывает его. Другие поля FCB заполняются MS-DOS, как описано ниже при открытии файла. Если операция открытия завершилась успешно, MS-DOS возвращает значение 0 в AL; если операция завершилась ошибочно в AL возвращается 0FFH. Эта функция, обычно, ошибочно не завершается, если файл не соз- дается в корневом каталоге, и этот каталог оказывается полным. П_р_е_д_у_п_р_е_ж_д_е_н_и_е: Чтобы избежать потери существующих данных, функция открытия FCB должна использоваться, чтобы проверить существование файла перед его созданием. 3.6. Открытие файла Функция 0FH открывает существующий файл. DS:DX должен указывать на правильный, неоткрытый FCB, содержащий имя открываемого файла. Если файл найден в текущем каталоге, MS-DOS открывает файл, заполняет FCB, как показано ниже, и возвращает управление с AL, установленным в 00H; если файл не найден, то AL устанавливается в 0FFH, указывая на ошибку. При открытии файла MS-DOS: - устанавливает идентификатор дисковода (смещение 00H) в действи- тельный дисковод (01 = A, 02 = B и так далее); - устанавливает номер текущего блока (смещение 0CH) в 0; - устанавливает размер файла (смещение 10H) в значение найденное в элементе каталога для файла; - устанавливает размер записи (смещение 0EH) равным 128; - устанавливает метку даты и времени (смещение 14H и 16H) в зна- чение, найденное в элементе каталога файла. Перед исполнением операций над записями может появиться необходи- мость изменения FCB, например, изменения размера записи и указателя записи при прямом доступе. П_р_и_м_е_р: Высветить подсказку и получить имя файла от пользо- вателя. Анализировать имя файла в FCB, проверяя на неправильный иден- тификатор или наличие шаблонных символов. Если вводится правильное непротиворечивое имя файла, попытаться открыть файл. Создать файл, ес- ли он еще не существует. kbuf db 64, 0, 64 dup (0) prompt db 0dh, 0ah, 'Enter filename: 5' myfcb db 37 dup (0) . . . ;высветить подсказку mov dx, seg prompt ; DS:DX = адрес подсказки mov ds, dx mov es, dx mov dx, offset prompt mov ah, 09h ;функция 09H = печать строки int 21h ;обратиться к MS-DOS ;ввести имя файла mov dx, offset kbuf ; DS:DX = адрес буфера mov ah, 0ah ;функция 0AH = ввести строку int 21h ; обратиться к MS-DOS ;анализировать имя файла в FCB mov si, offset kbuf+2; DS:SI = адрес имени файла mov di, offset myfcb ; ES:DI = адрес FCB mov fx, 2900h ;функция 29H = анализировать имя int 21h ;обратиться к MS-DOS or al, al ;переход, если неверный дисковод jnz error ;шаблонные символы в имени ;попытка открыть файл mov dx, offset myfcb ; DS:DX = адрес FCB mov ah, 0fh ;функция 0FH = открыть файл int 21h ;обратиться к MS-DOS or al, al ;создание успешно? jnz error ;переход по ошибке proceed: . ;файл был открыт или создан, . ;FCB готово для проведения . ;операций чтения/записи 3.7. Закрытие файла Функция 10H (закрытие файла с FCB) закрывает файл, ранее открытый с FCB. Как обычно, функция вызывается с DS:DX, указывающими на FCB файла, который должен быть закрыт. MS-DOS модифицирует каталог, если необходимо, отображает любые изменения в размере файла и последней за- писи даты и времени. Если операция завершается успешно, MS-DOS возвращает 00H в AL; в противном случае возвращает 0FFH. 3.8. Чтение и запись файлов при помощи FCB MS-DOS предлагает три основанных на FCB метода доступа для дан- ных: последовательный, произвольный по записям и произвольный по бло- кам. Последовательные операции позволяют перемещаться вдоль файла по одной записи за один раз. MS-DOS увеличивает номера текущей записи и текущего блока после каждого доступа к файлу так, что они указывают на начало следующей записи. Этот метод особенно полезен для копирования или печати файлов. Произвольный (или прямой) метод доступа позволяет программе чи- тать или писать записи в любое место файла без последовательного чте- ния записей до указанного места в файле. Программа должна установить поле номера произвольной записи в FCB перед запрашиваемым чтением или записью. Этот метод полезен в приложениях к базам данных, в которых программы должны манипулировать с записями фиксированной длины. Операции произвольного доступа по блокам объединяют свойства пос- ледовательного и произвольного методов доступа. Программа может уста- навливать номер записи, чтобы указать на произвольную запись внутри файла, и MS-DOS модифицирует номер записи после операции чтения или записи. Таким образом, последовательные операции могут легко начинать- ся с любой точки файла. Операции произвольного доступа по блокам с длиной записи, равной 1 байту, моделируют методы доступа к файлу по по системному идентификатору. Все три метода требуют, чтобы FCB для файла был открыт, чтобы ре- гистры DS:DX указывали на FCB, чтобы DTA была достаточно большой с учетом указанного размера записи, и чтобы адрес DTA был ранее установ- лен функцией 1AH, если не используется по умолчанию DTA в программном PSP (префикс программного сегмента). MS-DOS сообщает об успешном или неудачном завершении любой свя- занной с FCB операции чтения (последовательной, произвольной по записи или произвольной поблочной) одним из четырех кодов возврата в регистре AL. --------------------------------------------------------------- Код Значение -------------------------------------------------------------- 00H Успешное чтение 01H Достигнут конец файла; данные не прочитаны в DTA 02H переход через сегмент (DTA слишком близко расположена к концу сегмента); данные не прочитаны в DTA 03H Достигнут конец файла; часть данных прочитана в DTA --------------------------------------------------------------- MS-DOS сообщает об успешном или неудачном завершении связанной с FCB операции записи, возвращая один из трех кодов в регистре AL: -------------------------------------------------------------- Код Значение --------------------------------------------------------------- 00H Успешная запись 01H Диск полон; записана часть или нет записи 02H Переход через сегмент (DTA слишком близко расположена к концу сегмента); запись неуспешна -------------------------------------------------------------- Для основанных на FCB операций записи, записи размером меньше, чем один сектор (512 байт) прямо на диск не записываются. Вместо это- го, MS-DOS сохраняет записи во внутреннем буфере и записывает данные на диск только если внутренний буфер полон, если файл закрывается или когда выполняется вызов прерывания 21H, функция 0DH (восстановление диска). 3.8.1. Последовательный доступ: чтение Функция 14H (Последовательное чтение) читает записи последова- тельно из файла по текущему адресу DTA, которая должна указывать на область, не меньшую размера записи, определенного в FCB. После каждой операции чтения MS-DOS модифицирует номера записи и блока в FCB, чтобы указать на следующую запись (смещения 0CH и 20H). 3.8.2. Последовательный доступ: запись Функция 15H (Последовательная запись) последовательно записывает данные из памяти в файл. Записываемая длина определяется размером за- писи поля (смещение 0EH) в FCB; адрес памяти записи, которую надо за- писать, определяется текущим адресом DTA. После каждой операции после- довательной записи MS-DOS модифицирует номера блока и записи в FCB (смещение 0CH и 20H), чтобы указать на следующую запись. 3.8.3. Произвольный (прямой) доступ к записи: чте- ние Функция 21H (Произвольное чтение) читает определенную запись из файла. Перед запрашиванием операции чтения программа определяет читае- мую запись, устанавливая размер записи и ее номер в FCB (смещение 0EH и 21H). Адрес текущей DTA должен быть также предварительно установлен функцией 1AH, чтобы указывать на буфер адекватного размера, если DTA, принимаемая по умолчанию, недостаточно велика. После операции чтения MS-DOS устанавливает поля номеров текущей записи и текущего блока (смещение 0CH и 20H) на ту же запись. Таким образом, программа установлена для перехода к последовательному чте- нию/записи. Однако, если программа хочет продолжить работать в режиме произвольного доступа, она должна продолжить модификацию поля произ- вольной записи в FCB перед каждой операцией чтения/записи. 3.8.4. Произвольный доступ к записи: запись Функция 22H (Произвольная запись) записывает определенную запись из памяти в файл. Перед выполнением вызова функции программа должна обеспечить, чтобы подходящим образом были установлены размер записи и поля указателя произвольной записи со смещением 0EH и 21H FCB, и чтобы текущий адрес DTA указывал на буфер, содержащий записываемые данные. После записи MS-DOS устанавливает поля номеров текущей записи и текущего блока (смещение 0CH и 20H) на ту же запись. Таким образом, программа готова к последовательному чтению/записи. Если необходимо продолжить работу с произвольным доступом к записи, следует продолжить модификацию поля произвольной записи в FCB перед каждой операцией про- извольного чтения/записи. 3.8.5. Произвольный доступ к блоку: чтение Функция 27H (Произвольное чтение блока) читает блок последова- тельных записей. Перед тем, как выдать запрос на чтение, программа должна определить размещение записи файла устанавливая в FCB поля раз- мера записи и номера произвольной записи (смещение 0EH и 21H), и по- местить количество записей, которые необходимо прочитать, в CX. Если DTA, принимаемая по умолчанию, недостаточно велика, чтобы поместить группу записей, которые необходимо прочитать, необходимо установить адрес DTA с помощью функции 1AH так, чтобы указывать на достаточно большой буфер. Тогда программа может выполнить вызов функции 27H с указанием в DS:DX адреса FCB файла. После операции произвольного чтения блока MS-DOS переустанавлива- ет в FCB указатель на произвольную запись (смещение 21H) и поля номе- ров текущей записи и текущего блока (смещения 0CH и 20H), чтобы ука- зать на начало следующей непрочитанной записи, и возвращает количество записей, действительно прочитанных, в CX. Если размер записи устанавливается 1 байт, функция 27H читает указанное в CX число байтов, начиная с позиции байта, определенной в указателе произвольной записи. Это моделирует (в некоторой степени) операцию чтения по системному идентификатору (функция 3FH). 3.8.6. Произвольный доступ к блоку: запись Функция 28H (Произвольная запись блока) записывает блок последо- вательных записей из памяти на диск. Программа определяет размещение первой записи файла, которую нужно записать, устанавливая поля указа- теля произвольной записи и размера записи в FCB (смещение 0EH и 21H). Если не используется DTA по умолчанию, программа должна также обеспе- чить, чтобы адрес текущей DTA устанавливался в соответствии с предыду- щим вызовом функции 1AH. Когда вызывается функция 28H, регистры DS:DX должны указывать на FCB для файла и регистр CX должен содержать число записей для записи. После операции записи MS-DOS переустанавливает указатель произ- вольной записи FCB (смещение 21H) и поля номера текущей записи и теку- щего блока (смещение 0CH и 20H), чтобы указать на начало следующего блока данных, и возвращает число записей, действительно записанных, в CX. Если размер записи устанавливается 1 байт, функция 28H записывает число байтов, определенных в CX, начиная с позиции байта, определенной в указателе произвольной записи. Это моделирует (до некоторой степени) опрацию записи по системному идентификатору(функция 40H). Вызов функции 28H с нулевым счетчиком записи в регистре CX приво- дит к тому, что длина файла расширяется или усекается до текущего зна- чения в поле FCB указателя произвольной записи (смещение 21H), умно- женное на содержимое поля размера записи (смещение 0EH). П_р_и_м_е_р: Открыть файл MYFILE.DAT и создать файл MYFILE.BAK на текущем дисководе, скопировать содержимое файла .DAT в файл .BAK, ис- пользуя чтение и запись по 512 байт и затем закрыть оба файла. fcb1 db 0 ; дисковод по умолчанию db 'MYFILE ' ; 8 символов имени файла db 'DAT' ; 3 символа расширения db 25 dup (0) ; остаток fcb1 fcb2 db 0 ; дисковод по умолчанию db 'MYFILE ' ; 8 символов имени файла db 'BAK' ; 3 символа расширения db 25 dup (0) ; остаток fcb2 buff db 512 dup (?) ; буфер для ввода/вывода файла . . . ; открыть MYFILE.DAT... mov dx, seg fcb1 ; DS:DX = адрес FCB mov ds, dx ; mov dx, offset fcb1; mov ah, 0fh ; функция 0FH = открыть int 21h ; передать в MS-DOS or al, al ; открыт успешно? jnz error ; перейти, если открытие ; неуспешно ; создать MYFILE.BAK mov dx, offset fcb2; DS:DX адрес FCB mov ah, 16h ; функция 21H = создание int 21h ; передать в MS-DOS or al, al ; открыт успешно? jnz error ; перейти, если открытие ; неуспешно ; установить длину записи ; 512 байт mov word pti fcb1+0eh, 512 mov word pti fcb2+0eh, 512 ; установить DTA на наш буфер mov dx, offset buff; DS:DX = адрес буфера mov ah, 1ah ; функция 1AH = установить DTA int 21h ; передать в MS-DOS loop: ; читать MYFILE.DAT mov dx, offset fcb1; DS:DX = адрес FCB mov ah, 14h ; функция 14H = последов.чтение int 21h ; передать в MS-DOS or al,al ; было ли чтение успешным? jnz done ; если нет, то покинуть и ; записать MYFILE.BAK... mov dx,offset fcb2 ; DS:DX = адрес FCB mov ah,15h ; функция 15H = последов.запись int 21h ; передать в MS-DOS or al,al ; была ли запись успешной? jnz error ; переход, если записывпние ; неуспешно jmp loop ; продолжить до конца файла done: ; сейчас закрыть файлы... mov dx,offset fcb1 ; DS:DX = FCB для MYFILE.DAT mov ah,10h ; функция 10H = закрыть файл int 21h ; перейти в MS-DOS or al,al ; закрытие успешно? jnz error ; переход, если закрытие ; неуспешно mov dx,offset fcb2 ; DS:DX = FCB для MYFILE.BAK mov ah,10h ; функция 10H = закрыть файл int 21h ; переход в MS-DOS or al,al ; закрытие успешно? jnz error ; переход, если закрытие ; неуспешно 3.9. Другие операции с файлом, использующие FCB Как и с управлением файлом по системному идентификатору, MS-DOS обеспечивает FCB-ориентированные функции для переименования или удале- ния файла. В отличие от других основанных на FCB функций и их аналогов в управлении по системному идентификатору, эти две функции допускают использование шаблонных символов. Вдобавок FCB-функции позволяют опре- делять размер или существование файла без открытия файла. 3.9.1. Переименование файла Функция 17H (Переименование файла) переименовывает файл (или фай- лы) в текущем каталоге. Файл, который необходимо переименовать, не мо- жет иметь атрибуты "скрытый" или "системный". Перед вызовом функции 17H программа должна создать специальный FCB, который содержит код дисковода по смещению 00H, старое имя файла по смещению 01H и новое имя файла по смещению 11H. Как текущее, так и новое имя файла может содержать шаблонный символ '?'. При сделанном вызове функции, DS:DX должны указать на специальную структуру FCB. MS-DOS ищет текущий каталог для старого имени файла. Если система находит старое имя файла, MS-DOS ищет новое имя файла и при несовпадении имен изменяет элемент каталога для старого имени на новое имя файла. Если поле старого имени файла специального FCB содер- жит какой-либо шаблонный символ, MS-DOS переименовывает каждый подхо- дящий файл. Дублирование имен файлов не разрешается; процесс заверша- ется неуспешно при первом дублировании имени. Если операция завершается успешно, MS-DOS возвращает нуль в ре- гистр AL; если операция завершается неуспешно, то возвращается 0FFH. Условие ошибки может указывать, или что не было переименованных фай- лов, или что был переименован по крайней мере один файл, но операция была прекращена из-за дублирования имени. П_р_и_м_е_р: Переименовать все файлы с расширением .ASM в текущем каталоге дисковода по умолчанию в файлы с расширением .COD. renfcb db 0 ; дисковод по умолчанию db '????????' ; шаблонное имя файла db 'ASM' ; старое расширение db 5 dup (0) ; резервированная область db '????????' ; шаблонное имя файла db 'COD' ; новое расширение db 15 dup (0) ; остаток FCB . . . mov dx, ; DS:DX mov ds, dx ; специальное FCB mov dx, offset renfcb ; mov ah, 17h ; ф-я 17H переименовать int 21h ; передать в MS-DOS or al,al ; успешно ли заверш. FCB jnz error ; переход при неудачном ; завершении . . . 3.9.2. Удаление файла Функция 13H (Удалить файл) удаляет файл из текущего каталога. Файл не должен быть открыт каким-либо процессом. Если удаляемый файл имеет специальные атрибуты, такие как 'только для чтения', программа должна использовать расширенный FCB для удаления файла. Каталог не мо- жет быть удален этой функцией даже с расширенным FCB. Функция 13H вызывается с DS:DX указывающих на неоткрытые допусти- мые FCB, содержащие имя файла, который необходимо удалить. Имя файла может содержать шаблонный символ, если файл существует. MS-DOS удаляет все файлы, соответствующие определяемому имени. Если по крайней мере один файл соответствует FCB и удален, то MS-DOS возвращает 00H в AL, если не найдены соответствующие имена файлов, то MS-DOS возвращает 0FFH. З_а_м_е_ч_а_н_и_е: Эта функция, если завершается успешно, не воз- вращает информации о том, какие файлы и как много файлов было удалено. Когда нужно удалить множество файлов, контроль закрытия может быть вы- полнен по использовании функции Find File (нахождение файла функцией 11H и 12H), чтобы опросить имена файлов кандидатов. См.: "ПРОГРАММИРО- ВАНИЕ В СРЕДЕ MS-DOS:Программирование для MS-DOS: Каталоги диска и метки тома". Файлы могут быть удалены тогда индивидуально. П_р_и_м_е_р: Удалить все файлы в текущем каталоге текущего диско- вода, имеющие расширение .BAK и те файлы, чьи имена начинаются буквой А. delfcb db 0 ; дисковод по умолчанию db 'A???????' ; шаблонное имя файла db 'BAK' ; расширение db 25 dup (0) ; остаток FCB . . . mov dx, seg delfcb ; DS:DX = адрес FCB mov ds, dx ; mov dx, offset delfcb ; mov ah, 13h ; функция 13H = удалить int 21h ; переход в MS-DOS or al, al ; выполнена ли функция успешно jnz error ; переход, если удаление ; неуспешно . . . 3.9.3. Нахождение размера файла и проверка его су- ществования Функция 23H (Получить размер файла) используется, чтобы найти размер файла на диске без его открытия, но она может использоваться вместо функции 11H (Найти первый файл) для простой проверки существо- вания файла. Перед вызовом функции 23H программа должна проверить имя файла в неоткрытом FCB, инициализировать поле размера записи FCB (сме- щение 0EH) и установить регистры DS:DX, чтобы указать на FCB. При возврате функции 23H регистр AL содержит 00H, если файл был найден в текущем каталоге определенного дисковода, и 0FFH, если файл не найден. Если файл найден, поле произвольной записи FCB (смещение 21H) со- держит число записей (округленное в сторону увеличения) в целевом фай- ле в терминах значений поля размера записи (смещение 0EH) FCB. Если размер записи содержит по крайней мере 64 байта, то используются толь- ко первые три байта поля произвольной записи; если же размер записи меньше, чем 64 байта, используются все 4 байта. Чтобы получить размер файла в байтах, программа должна установить поле размера записи в 1 перед вызовом. Такой метод не быстрее, чем просто открытие файла, но при его использовании избегается последующая операция закрытия файла (которая требуется в сетевой среде). 4. Заключение MS-DOS поддерживает два различных, но перекрывающихся по возмож- ностям множества средств управления файлом и записью. Операции, ориен- тированые на управление по системному идентификатору, используют имена файлов с нулевым окончанием (ASCIIZ) и 16-битные идентификаторы фай- лов, которые MS-DOS возвращает после открытия или создания файла. Име- на файлов могут включать полный путь определения размещения файлов в иерархической структуре каталога. Информация, связанная с управлением по системному идентификатору, такая как текущий указатель чтения/запи- си для файла, дата и время последней записи в файл и разрешение на чтение/запись файла, режим разделения и атрибуты, содержится во внут- ренней таблице MS-DOS. В противоположность этому, FCB-ориентированные функции используют 37-байтную структуру, называемую блоком управления файлом и размещен- ную в пространстве памяти прикладной программы, для определения имени и размещения файла. После того, как файл открыт или создан, FCB ис- пользуется как операционной системой MS-DOS, так и прикладными прог- раммами, чтобы иметь дополнительную информацию о файле, такую как те- кущий указатель чтения/запись файла. Поскольку FCB введены ранее ие- рархической структуры каталога, введенной в версии 2.0 MS-DOS, и не имеют места для хранения пути к файлу, функции, основанные на FCB, не могут использоваться для доступа к файлам, не находящимся в текущем каталоге специфицированного дисковода. Вдобавок к недостаткам, касающимся поддержки имени пути, основан- ные на FCB функции имеют более бедные диагностические сообщения об ошибках, чем функции управления по системному идентификатору, и беспо- лезны в сетевой среде, так как не поддерживают режимы разделения и блокирования доступа к файлам. Следовательно, в новых прикладных зада- чах настоятельно рекомендуется использовать функции, основанные на уп- равлении по системному идентификатору. Роберт Байер Программировал: Рэй Дункан Глава 8. Дисковые каталоги и метки тома MS-DOS, как дисковая операционная система, предусматривает средс- тва для каталогизирования файлов диска. Структура данных, используемая MS-DOS для этой цели, является каталогом, или линейным списком имен, в котором каждое имя связано с физическим размещением на диске. Каталоги доступны и обновляются всякий раз, когда происходит манипулирование файлами, однако, как каталогом, так и их содержимым можно манипулиро- вать явно, используя разные сервисные функции прерывания 21H MS-DOS. Версии 1.x MS-DOS содержат только один каталог на каждом диске. Версии 2.0 и более поздние содержат многочисленные каталоги, связанные в двухстороннюю иерархическую древовидную структуру (Рис.8-1), и пол- ная спецификация названия каталогизированного файла, таким образом, должна описывать размещение в иерархии каталогов, в которой это назва- ние появляется. Эта спецификация, или маршрут, создается составлением указателя драйвера диска (например, A: или C:); названий каталогов в иерархическом порядке, начиная с корневого каталога и, наконец, имени файла или каталога. Например, на Рис.8-1 полное название маршрута для FILE5.COM будет C:\ALPHA\GAMMA\FILE5.COM Два примера FILE1.COM в корневом каталоге и в каталоге EPSILON отличаются названиями своих маршрутов: C:\FILE1.COM в первом случае и C:\BETA\EPSILON\ FILE1.COM во втором. П_р_и_м_е_ч_а_н_и_е: Если не определено устройство, то принимает- ся текущее устройство. Кроме того, если первому имени в спецификации не предпосылается знак "\" , то спецификация принимается соответствую- щей текущему каталогу. Например, если текущим каталогом является C:\BETA\EPSILON, то спецификация \FILE1.COM означает файл FILE1.COM в корневом каталоге, а спецификация FILE1.COM означает файл FILE1.COM в каталоге C:\BETA\EPSILON. Смотрите Рис.8-1. Хотя обычный пользователь MS-DOS не должен вникать в особенности организации иерархической структуры каталогов, программисты MS-DOS должны понимать внутреннюю структуру каталогов и функции прерывания 21H, предназначенные для обращения с содержанием каталогов и поддержа- ния связей между каталогами. В этой главе приведена необходимая инфор- мация по этому вопросу. C:\ (корневой каталог) -------------------------¬ ¦ подкаталог ALPHA ¦ ¦ подкаталог ALPHA ¦ ¦ файл FILE1.COM ¦ ¦ файл FILE2.COM ¦ L------------T------------ +-------------------------------¬ ¦ ¦ C:\ALPHA C:\BETA -------------------------¬ ------------------------¬ ¦ подкаталог . ¦ ¦ подкаталог . ¦ ¦ подкаталог .. ¦ ¦ подкаталог .. ¦ ¦ подкаталог GAMMA ¦ ¦ подкаталог EPSILON ¦ ¦ подкаталог DELTA ¦ ¦ файл FILE4.COM ¦ ¦ файл FILE3.COM ¦ L------------------------ L------------T------------ ¦ +--------------¬ ¦ ¦ ¦ ¦ C:\ALPHA\GAMMA ¦ C:\BETA\EPSILON -------------------------¬ ¦ ------------------------¬ ¦ подкаталог . ¦ ¦ ¦ подкаталог . ¦ ¦ подкаталог .. ¦ ¦ ¦ подкаталог .. ¦ ¦ файл FILE5.COM ¦ ¦ ¦ файл FILE1.COM ¦ L------------------------- ¦ L------------------------ ¦ ¦ C:\ALPHA\DELTA -----------------------¬ ¦ подкаталог . ¦ ¦ подкаталог .. ¦ L----------------------- Рисунок 8-1. Типичная иерархическая каталоговая структура (версии MS-DOS 2.0 и более поздние). рис. 1 1. Логическая структура каталогов MS-DOS Каталог MS-DOS состоит из перечня 32-байтовых каталоговых статей, каждая из которых содержит название и описательную информацию. В вер- сиях 1.x MS-DOS каждое имя должно быть именем файла, в версиях 2.0 и более поздних в статьях каталога, кроме того, могут появляться метки томов и имена каталогов. 1.1. Поиски каталогов Каталоговые статьи не сортируются и не связываются в списковые структуры. Таким образом, когда MS-DOS осуществляет поиск каталога по имени, он должен происходить линейно, начиная с первого имени в ката- логе. В версиях 1.х MS-DOS каталоговый поиск продолжается до тех пор, пока не будет найдено определенное имя либо не будет рассмотрена каж- дая статья в каталоге. В версиях 2.0 и более поздних поиск продолжает- ся до тех пор, пока не будет обнаружено определенное имя либо пока не встретится пустая статья каталога (то есть такая, первый байт которой является нулем). Эта пустая статья обозначает логический конец катало- га адресов. 1.2. Добавление и уничтожение статей каталога MS-DOS уничтожает статью каталога, отмечая 0E5H ее первый байт, а не уничтожая ее либо исключая ее из каталога. Новые имена добавляются в каталог в путем использования заново первую удаленную статью в спис- ке. Если удаленных статей нет, то MS-DOS добавляет новую статью в ко- нец списка. 1.3. Текущий каталог Когда на диске находится более одного каталога, MS-DOS сохраняет связь со стандартным каталогом поиска, известным, как текущий каталог. Текущий каталог используется во всех каталоговых поисках, например, при открытии файла, если не определен другой путь. При начальном дейс- твии MS-DOS делает корневой каталог текущим каталогом, а любой другой каталог может быть обозначен позже либо с помощью команды CHDIR либо в результате работы, используя функцию 3BH прерывания 21H (Изменить те- кущий каталог). 2. Формат каталога Корневой каталог создается программой MS-DOS FORMAT. См: "Команды пользователя: FORMAT". Программа FORMAT размещает корневой каталог не- посредственно после таблиц размещения файлов диска (File Allocation Tables - FATs). Кроме того, программа FORMAT определяет размер корне- вого каталога. Размер зависит от объема памяти: программа FORMAT раз- мещает более крупные корневые каталоги на постоянных дисках большой емкости, а меньшие корневые каталоги на гибких дисках. В то же время размер подкаталогов ограничивается лишь объемом памяти диска, так как память для подкаталогов распределяется динамически, как для любого файла MS-DOS. Размер и физическое расположение корневого каталога мо- гут быть получены из блока параметров BIOS (BIOS Parameter Block - BPB), находящегося в секторе начальной загрузки (boot-сектор). См: "Программирование в среде MS-DOS: Структура MS-DOS: Запоминающие уст- ройства MS-DOS." Поскольку память для корневого каталога распределяется только после форматизации диска, коорневой каталог не может быть стерт либо перемещен. Подкаталоги, память для которых на диске выделяется динами- чески, могут быть при необходимости добавлены либо стерты. 2.1. Формат входа каталога Каждый 32-байтовый вход каталога состоит из семи полей, содержа- щих имя, байт атрибутов, дату и время и информацию о размере файла и его физическом расположении на диске (См. Рис.8-2). В следующих параг- рафах описано, как форматизуются поля. Байт 0 0BH 0CH 16H 18H 1AH 1CH 1FH ------T-------T-------T-----T----T--------T------¬ ¦ Имя ¦ Атри- ¦(Заре- ¦Время¦Дата¦ Началь-¦Размер¦ ¦ ¦ буты ¦зерви- ¦ ¦ ¦ный кла-¦файла ¦ ¦ ¦ ¦ровано)¦ ¦ ¦ стер ¦ ¦ L-----+-------+-------+-----+----+--------+------- Рисунок 8-2. Формат входа в каталог Поле имени (байты 0-0AH) содержит 11-байтное имя, если первый байт поля не указывает на то, что вход в каталог удален или пуст. Имя может быть 11-байтным именем поля (8-ми байтовое имя, за которым сле- дует 3-байтное расширение), 11-байтным именем подкаталога (8-байтное имя, за которым следует 3-байтное расширение) или 11-байтной меткой тома. Имена менее восьми байтов и расширения менее трех байтов допол- няются справа пробелами так, чтобы расширение всегда оказывалось в байтах 08-0AH поля имени. Первый байт поля имени может содержать опре- деленные зарезервированные значения, которые влияют на то, как MS-DOS обрабатывает вход в каталог: _____________________________________________________________________ ¦ Значение ¦ Смысл __________¦__________________________________________________________ ¦ 0 ¦ Пустой вход в каталог (логический конец каталога в верси- ¦ ях MS-DOS 2.0 и более поздних) 5 ¦ Первый знак имени,который необходимо показывать как знак, ¦ представленный кодом 0E5H (версия 3.2 MS-DOS) 0E5H ¦ Удаленный вход в каталог __________¦__________________________________________________________ Когда MS-DOS создает подкаталог, в него всегда включаются два имени-синонима (алиаса), которые занимают первые два входа в создавае- мом каталоге. Имя "." (ASCII-точка) является алиасом для имени текуще- го каталога; имя ".." (две ASCII-точки) является алиасом для основного каталога, то есть каталога, в котором найден вход, содержащий имя те- кущего каталога. Поле атрибутов (байт 0BH) описывает, как MS-DOS управляет данным входом в каталог (Рис. 8-3). Каждый бит в поле атрибутов обозначает отдельный характерный признак этого входа в каталоге и может быть ус- тановлен независимо. За один раз можно установить несколько битов. Бит 7 6 5 4 3 2 1 0 -------T------T------T------T-----T-------T-----T------¬ ¦(заре-¦(заре-¦ ¦ ¦ ¦Систем-¦Скры-¦Посто-¦ ¦зерви-¦зерви-¦Архив ¦Подка-¦Метка¦ный ¦тый ¦янный ¦ ¦рова- ¦рова- ¦ ¦талог ¦тома ¦файл ¦файл ¦файл ¦ ¦но) ¦но) ¦ ¦ ¦ ¦ ¦ ¦ ¦ L------+------+------+------+-----+-------+-----+------- Рисунок 8-3. Формат поля атрибутов входа каталога Бит "только для чтения" (бит 0) установлен в 1, чтобы обозначить файл, предназначенный только для чтения. Функция 3DH прерывания 21H (Открыть файл с идентификатором) закончится неуспешно, если попытается открыть этот файл для записи. Бит скрытого файла (бит 1) должен быть установлен в 1, чтобы показать, что вход должен быть пропущен в обыч- ных каталоговых поисках, то есть в каталоговых поисках, которые не требуют, чтобы скрытые входы были включены в поиск. Бит системного файла (бит 2) установлен в 1, чтобы указать, что вход относится к фай- лу, используемому операционной системой. Подобно биту скрытого файла системный бит исключает вход в каталог из обычных каталоговых поисков. Бит метки тома (бит 3) установлен в 1, чтобы указать, что вход в ката- лог представляет метку тома. Бит подкаталога (бит 4) установлен в 1, когда вход в каталог содержит имя и информацию о расположении другого каталога. Этот бит всегда устанавливается для входов в каталоги, кото- рые соответствуют текущему каталогу (.) и основному каталогу (..). Ар- хивный бит (бит 5) установлен в 1 функциями MS-DOS, которые закрывают файл, в который осуществлялся вывод. Одних операций открытия и закры- тия файла недостаточно, чтобы модернизировать архивный бит во входе каталогов файлов. Когда вход в каталог создан, MS-DOS инициализирует поля времени и даты (байты 16-17H и 18-19H). Эти поля модифицируются всякий раз, ког- да записывается файл. Форматы этих полей показаны на Рис.8-4 и 8-5. Бит 15 10 4 0 ----------------T-----------------T-------------------¬ ¦ ¦ ¦ ¦ ¦ Часы (0-23) ¦ Минуты (0-59) ¦ Двухсекундные ¦ ¦ ¦ ¦ приращени (0-29) ¦ L---------------+-----------------+-------------------- Рисунок 8-4. Формат поля времени во входе в каталог. Бит 15 10 4 0 ----------------T-----------------T-------------------¬ ¦ ¦ ¦ ¦ ¦ Год (относи- ¦ Месяц (1-12) ¦ День (1-31) ¦ ¦ тельно 1980) ¦ ¦ ¦ L---------------+-----------------+-------------------- Рисунок 8-5. Формат поля даты во входе в каталог. Поле начального кластера (байты 1A-1BH) обозначает расположение на диске первого кластера, принадлежащего файлу. Этот номер кластера мо- жет быть использован как точка входа в таблицу размещения файлов (FAT) для диска. (Номера кластеров могут быть преобразованы в номера логи- ческих секторов с помощью информации, содержащейся в блоке пара- мет- ров BIOS BPB.) Для входа . (алиас для каталога, который содержит вход) поле на- чального кластера содержит номер начального кластера самого каталога. Для входа в ..(алиас для основного каталога) значение в поле начально- го кластера относится к основному каталогу, если основной каталог не является корневым каталогом, поскольку тогда номер начального кластера равен нулю. Поле размера файла (байты 1C-1FH) является целым 32-разрядным числом, которое указывает размер файла в байтах. 3. Метки тома Общий термин "том" относится к единице вспомогательной памяти, как например, гибкий диск, постоянный диск или магнитная лента. В ком- пьютерных средах, где могут использоватся много различных томов, опе- рационная система может однозначно идентифицировать каждый том, обоз- начая его меткой тома. В версиях 2.0 MS-DOS и более поздних метки томов устанавливаются как особый тип входа в каталог, определенного установкой третьего бита в 1 в поле атрибутов. Во входе каталога метки тома поле имени содержит строку в 11 байт, указывающую имя тома. Метка тома может появляться только в корневом каталоге диска, и на любом конкретном диске может присутствовать только одна метка тома. В версиях 2.0 MS-DOS и более поздних команда FORMAT может приме- няться с ключом /V , чтобы инициализировать диск меткой тома. В верси- ях 3.0 и более поздних команда LABEL может применяться для того, чтобы создать, изменить или стереть метку тома. Несколько команд, включая VOL, DIR, LABEL, TREE и CHKDSK, могут отображать на диске метку тома диска. См: "Команды пользователя". В версиях 2.х MS-DOS метки томов - просто удобство для пользова- теля, на практике MS-DOS не использует метку тома с какой либо другой целью. Однако, в версиях 3.х MS-DOS команда SHARE исследует метку тома диска, когда пытается проверять, не оказался ли случайно том диска за- менен во время операций чтения и записи файла. Удаляемым томам дисков, следовательно, должны назначаться уникальные имена томов, если их со- бираются использовать для хранения коллективных файлов. 4. Функциональная поддержка для каталогов MS-DOS Некоторые обслуживающие программы прерывания 21H могут быть по- лезны программистам, которым необходимо управлять каталогами и их со- держанием (см. табл. 8-1). Эти программы можно разделить на две кате- гории: те программы, которые используют модифицированный управляющий блок (FCB) для передачи имен файлов обслуживающим программам прерыва- ния 21H (Функции 11H, 12H, 17H и 23H), и те, которые используют иерар- хический маршрут спецификаций (функции 39H, 3AH, 3BH, 43H, 47H, 4EH, 4FH и 57H).См: "Программирование в среде MS-DOS: Программирование для MS-DOS: Управление записями и файлами; Системные вызовы: Прерывание 21H". Функции, которые используют FCB, требуют, чтобы вызывающая прог- рамма сохраняла достаточную память для расширенного FCB до того, как вызвана функция прерывания 21H. Вызывающая программа инициализирует поля имени файла и расширения в FCB и передает адрес FCB в обслуживаю- щую программу MS-DOS в DS:DX. Функции, которые используют имена марш- рутов, предполагают, что все имена маршрутов представлены в формате ASCIIZ, то есть за последним знаком имени должен последовать нулевой байт. Имена в названиях маршрутов, переданные функциям прерывания 21H, могут быть разделены либо знаком (\), либо знаком (/). (Знак / исполь- зуется в названиях маршрутов систем UNIX/XENIX.) Например, названия маршрута C:/MSP/SOURCE/ROSE.PAS и C:\MSP\SOURCE\ROSE.PAS эквивалентны, когда передаются функциям прерывания 21H. Таким образом, знак / может быть использован в имени маршрута в программе, которая должна выпол- няться как в MS-DOS, так и в UNIX/XENIX. Однако, командный процессор MS-DOS (COMMAND.COM) распознает как знак разделителя имени маршрута только знак \ , поэтому знак / не может быть использован как раздели- тель в записи командной строки. Таблица 8-1. Функции MS-DOS для доступа к каталогам __________________________________________________________ ¦ ¦ ¦ Функция ¦ Вызывается с ¦ Возвращает ¦ Комментарий ________¦______________¦__________________¦_______________ Поиск AH=11H AL=0 (вход в ка- Если умолчание первого DS:DX=указа- талог найден) не удовлетво- файла тель неот- или 0FFH ( не ряет, следует крытого найден) перед исполь- FCB DTA видоизменен зованием уста- INT 21H (если вход в новить DTA каталог найден) Поиск AH=12H AL=0 (вход в ка- Использовать следую- DS:DX=указа- талог найден) тот же FCB щего тель неот- или 0FFH (не для функции файла крытого найден) 11H и функции FCB DTA видоизменен 12H INT 21H (если вход в каталог найден) Переиме- AH=17H AL=0 (файл пере- нование DS:DX=указа- именован) или файла тель видо- 0FFH (не найде- измененно- но имя файла в го FCB каталоге или INT 21H дублируется имя файла) Получе- AH=23H AL=0 (вход в ка- ние раз- DS:DX=указа- талог найден) мера тель неот- или 0FFH (не файла крытого найден) FCB в FCB модифициро- INT 21H вано поле числа записей в файле Создание AH=39H Установлен флаг каталога DS:DX=указа- состояния (если тель назва- ошибка) ния маршру- АХ=код ошибки та в виде (если ошибка) строки ASCIIZ INT 21 Удаление AH=3AH Установлен флаг каталога DS:DX=указа- состояния (если тель наз- ошибка) вания мар- АХ=код ошибки шрута в ви- (если ошибка) де строки ASCIIZ INT 21 Изменение AH=3BH Установлен флаг текущего DS:DX=указа- состояния (если каталога тель наз- ошибка) вания мар- АХ=код ошибки шрута в ви- (если ошибка) де строки ASCIIZ INT 21 Запрос/ AH=43H Установлен флаг Не может быть установ- AL=0 (полу- состояния (если использовано ка атри- чить атри- ошибка) для видоизме- бутов буты) АХ=код ошибки нения битов файла AL=1 (уста- (если ошибка) метки тома новить или подката- атрибуты) CХ=поле атрибутов га CХ=атрибуты из входа (если AL=1) каталога (если DS:DX=указа- вызвано с AL=0) тель назва- ния маршру- та в виде строки ASCIIZ INT 21 Поиск AH=47H Установлен флаг текущего DS:SI=указа- состояния (если каталога тель 64-х ошибка) байтового АХ=код ошибки буфера (если ошибка) DL=номер устройства В буфер заносится INT 21H название маршрута текущего ка- лога Поиск AH=4EH Установлен флаг Если умолчание первого DS:DX=указа- состояния (если не удовлетворя- файла тель наз- ошибка) ет, вания мар- АХ=код ошибки перед использо- шрута в ви- (если ошибка) ванием этой де строки DTA модифицирован функции следует ASCIIZ установить DTA CХ=атрибуты файла для сопостав- ления INT 21H Поиск AH=4FH Установлен флаг следую- INT 21H состояния (если щего ошибка) файла АХ=код ошибки (если ошибка) DTA модифицирован Переиме- AH=56H Установлен флаг нование DS:DX=указа- состояния (если файла тель наз- ошибка) вания мар- АХ=код ошибки шрута в ви- (если ошибка) де строки ASCIIZ ES:DI=указа- тель ново- го назва- ния марш- рута в ви- де строки ASCIIZ INT 21H Запрос/ AH=57H Установлен флаг установ- AL=0 (Запро- состояния (если ка даты/ сить дату/ ошибка) времени время) АХ=код ошибки AL=1 (уста- (если ошибка) новить дату/время) ВХ=системный идентифика- тор файла CХ=время (ес- ли AL=1) DX=дата (если AL=1) INT 21H _________________________________________________________ 4.1. Поиск в каталоге Для поиска в каталоге пригодны две пары функций прерывания 21H. Функции 11H и 12H используют FCB для передачи имен файлов в MS-DOS; эти функции имеются во всех версиях MS-DOS, но они не могут быть ис- пользованы с именами маршрутов. Функции 4EH и 4FH поддерживают назва- ния маршрутов, однако в версиях 1.х MS-DOS они отсутствуют. Все четыре функции нуждаются в адресе области передачи данных диска (DTA) до об- ращения к функции. Когда используются функции 12H и 4FH, текущая DTA должна быть такой же, как и DTA для предшествующего обращения к функ- циям 11H или 4EH. Функции поиска каталога прерывания 21H должны использоваться па- рами. Функции поиска первого файла возвращают первый найденный вход в текущем каталоге (функция 11H) либо в специфицированном каталоге (фун- кция 4EH). Функции поиска следующего файла (функции 12H и 4FH) могут быть вызваны повторно после успешного обращения к соответствующей фун- кции поиска первого файла. Каждое обращение к одной из функций поиска следующего файла возвращает следующий вход в каталог, совпадающий с заданным в функции поиска первого файла. Таким образом, схему поиска в каталоге можно представить следующим образом: call "find first file" function (вызвать функцию поиcка пер- вого файла) while (matching directory entry returned) (в цикле обращаться к функ- call "find next file" function ции поиска следующего файла до тех пор, пока возвраща- ется успешный признак поис- ка) 4.1.1. Шаблонные символы Эта стратегия поиска используется, поскольку спецификации имён могут включать шаблонные символы ?, которые маскируют какой-либо один знак, и * (смотрите ниже). Когда в имени одной из функций поиска пер- вого файла оказываются один или более шаблонные символов , то при по- иске в каталоге принимают участие только незамаскированные символы в имени. Таким образом, например, спецификация FOO? соответствует именам файлов FOO1,FOO2 и т.д.; спецификации FOO?????.??? подходит FOO4.COM, FOOBAR.EXE и FOONEW.BAK, так же как и FOO1 и FOO2; спецификации ????????.ТХТ подходят все файлы с расширением ТХТ; спецификации ????????.??? подходят все файлы каталога. Кроме того, функция 4EH распознает шаблонный символ *, который соответствует любому знаку в имени файла или расширении. MS-DOS заме- няет этот шаблонный символ, по сути дела, на соответствующее количест- во вопросительных знаков. Таким образом, например, спецификация FOO* эквивалентна FOO?????; спецификация FOO*.* эквивалентна FOO?????.???; и, конечно, спецификация *.* эквивалентна ????????.???. 4.2. Анализ входа каталога Все четыре функции прерывания 21H поиска в каталоге возвращают имя, атрибуты, размер файла, поля времени и даты для каждого найденно- го входа каталога. Эти данные помещаются в текущую DTA, хотя для двух пар функций форматы ответа различны: функции 11H и 12H возвращают в DTA копию 32-байтного входа каталога, включая номер кластера; функции 4EH и 4FH возвращают 43-байтную структуру данных, которая не содержит номер начального кластера. См.: "Системные вызовы: Прерывание 21H: Функция 4EH". Поле атрибутов входа каталога может быть проанализировано с по- мощью функции 43H (Запрос/установка атрибутов файла). Кроме того, фун- кция 57H (Запрос/установка даты/времени создания файла) может приме- няться для анализа времени и даты создания файла. Однако, в отличие от других рассматриваемых здесь функций, функция 57H предназначается только для файлов, которые активно используются внутри прикладных программ, то есть функция 57H может быть вызвана для анализа времени создания файла или печати даты только после того, как файл открыт или создан с помощью функций прерывания 21H, возвращающих системный иден- тификатор (функции 3CH, 3DH, 5AH или 5BH). 4.3. Видоизменение входа каталога Четыре функции прерывания 21H могут видоизменять содержание входа каталога. Функция 17H (Переименование файла) может использоваться для изменения поля имени в каком-либо входе каталога, включая скрытые или системные файлы, подкаталоги и метку тома. Альтернативная функция 56H (Переименование файла) также изменяет поле имени, однако, не может пе- реименовать метку тома или скрытый либо системный файл. Однако, она может использоваться для перемещения входа каталога из одного каталога в другой. (Эта способность ограничивается только именами файлов; под- каталоговые входы не могут быть перемещены функцией 56H). Функции 43H (Запрос/установка атрибутов файла) и 57H (Запрос/ус- тановка даты/времени создания файла) могут использоваться для видоиз- менения специфических полей входа каталога. Функция 45H может отметить вход каталога как скрытый или системный файл, хотя она не может видо- изменять метку тома или разряды подкаталога. Функция 57H, как отмеча- лось выше, может быть использована только с предварительно открытым файлом, она дает способ прочесть или видоизменить время и дату файла без вывода в файл. 4.4. Создание и уничтожение каталогов Функция 39H (Создание каталога) существует только для создания каталогов, то есть входов каталогов с разрядом подкаталога, установ- ленным в 1. (Функции прерывания 21H, которые создают файлы, как, нап- ример, функция 3CH, не могут присваивать атрибут подкаталога входу ка- талога.) Обратная функция 3AH (Удаление каталога) удаляет вход подка- талога из каталога (подкаталог должен быть совершенно пуст). Кроме то- го, функции прерывания 21H, которые удаляют файлы из каталогов, как, например, функция 41H, не могут быть использованы для удаления подка- талогов. 4.5. Определение текущего каталога Обращение к функции 47H прерывания 21H (Поиск текущего каталога) возвращает название маршрута текущего каталога, используемого в MS-DOS, в буфер, указанный пользователем. Обратная операция, в которой новый каталог может быть указан MS-DOS как текущий, совершается функ- цией 3BH (Изменение текущего каталога). 4.6. Примеры программирования: поиск файлов Подпрограммы на Рис. 8-6 (ниже) иллюстрируют функции 4EH и 4FH, которые используют спецификации маршрутов, передаваемые как строки ASCIIZ для поиска файлов. На Рис.8-7 эти ассемблерные программы ис- пользуются в простой программе на языке Си, которая печатает атрибуты, связанные с каждым входом текущего каталога. Отметим, как совершается поиск в каталоге в цикле WHILE на Рис.8-7 с использованием глобальной шаблонной спецификации файла (*.*) и повторяющимся выполнением функции FindNextFile ( ), пока находятся имена файлов. (См.: "Пример програм- мирования: Видоизменение метки тома для примеров функций поиска 11H и 21H, основанных на FCB".) TITLE 'DIRS.ASM' ; ; Подпрограммы для DIRDUMP.C ; ARG1 EQU [bp + 4] ;адресация стека для аргумен- ARG2 EQU [bp + 6] ;тов С-программы _TEXT SEGMENT byte public 'CODE' ASSUME cs:_TEXT ;--------------------------------------------------------- ; ; void SetDTA( DTA ); ; char *DTA; ; ;--------------------------------------------------------- PUBLIC _SetDTA _SetDTA PROC near push bp mov bp,sp mov dx,ARG1 ; DS:DX--DTA mov ah,1Ah ; AH=номер функции ; INT 21H int 21h ; Передать DTA в MS-DOS pop bp ret _SetDTA ENDP ;--------------------------------------------------------- ; ; int GetCurrentDir ( *path ); /* возвращает код ; ошибки */ ; char *path; /* указатель на буфер, ; содержащий путь */ ; ;--------------------------------------------------------- PUBLIC _GetCurrentDir _GetCurrentDir PROC near push bp mov bp,sp push si mov si,ARG1 ; DS:SI - буфер xor dl,dl ; DL=0 (имя устройства по ; умолчанию) mov ah,47h ; AH = номер функции ; INT 21H int 21h ; вызов MS-DOS; ; AX = код ошибки jc L01 ; переход по ошибке xor ax,ax ; нет ошибки, воз- ; вращает AX=0 L01 pop si pop bp ret _GetCurrentDir ENDP ;--------------------------------------------------------- ; ; int FindFirstFile(path,attribute); /* возвращает код ; ошибки */ ; char *path; ; int attribute; ; ;--------------------------------------------------------- PUBLIC _FindFirstFile _FindFirstFile PROC near push bp mov bp,sp mov dx,ARG1 ; DS:DX - маршрут mov cx,ARG2 ; CX= атрибут mov ah,4Еh ; AH=номер функции ; INT 21H int 21h ; вызов MS-DOS; ; AX = код ошибки jc L02 ; переход, если ошибка xor ax,ax ; нет ошибки, воз- ; вращает AX=0 L02 pop bp ret _FindFirstFile ENDP ;--------------------------------------------------------- ; ; int FindNextFile( ); /* возвращает код ; ошибки */ ; ;--------------------------------------------------------- PUBLIC _FindNextFile _FindNextFile PROC near push bp mov bp,sp mov ah,4Fh ; AH=номер функции ; INT 21H int 21h ; вызов MS-DOS; ; AX = код ошибки jc L03 ; переход, если ошибка xor ax,ax ; нет ошибки, воз- ; вращает AX=0 L03 pop bp ret _FindNextFile ENDP _TEXT ENDS _DATA SEGMENT word public 'DATA' CurrentDir DB 64 dup ( ) DTA DB 64 dup ( ) _DATA ENDS END Рисунок 8-6. Подпрограммы, иллюстрирующие функции 4EH и 4FH прерывания 21H. /* DIRDUMP.C */ #define AllAttributes 0x3F /* устанавливаются биты для всех атри- бутов */ main ( ) { static char CurrentDir [64]; int ErrorCode; int FileCount = 0; struct { char reserved [21]; char attrib; int time; int date; long size; char name [13]; } DTA; /* высветить имя текущего каталога */ ErrorCode = GetCurrentDir ( CurrentDir ); if ( ErrorCode ) { printf ( "\nError &d: GetCurrentDir", ErrorCode ); exit (1); } printf( "\nCurrent directory is \\&s", CurrenrDir ); /* высветить файлы и атрибуты */ SetDTA ( &DTA ); * передать DTA в MS-DOS * ErrorCode = FindFirstFile( "*.*", AllAttributes ); while( !ErrorCode ) { printf( "\n&12s -- ", DTA.name ); ShowAttributes( DTA.attrib ); ++FileCount; ErrorCode = FindNextFile ( ); } /* высветить счетчик файлов и закончить */ printf( "\n В текущем каталоге %d файлов\n",FileCount); return ( 0 ); } ShowAttributes ( a ) int a; { int i; int mask = i; static char AttribName[]; { "read-only ", "hidden ", "system ", "volume ", "subdirectory ", "frchive ", } for ( i = 0; i < 6; i++ ) /* проверка каждого бита атрибута */ { if( a & mask ) printf( AttribName[i] ); /* вывести сообщение, если бит установлен */ mask = mask << 1 ; } } Рисунок 8-7. Полная программа DIRDUMP.C 4.7. Пример программирования: изменение метки тома Для того, чтобы создать, видоизменить или уничтожить метку тома должны быть использованы функции прерывания 21H, которые работают с FCB. Рис.8-8 содержит четыре подпрограммы, которые показывают, как ис- кать, переименовывать, создавать или уничтожать метку тома в версиях 2.0 MS-DOS и более поздних. TITLE 'VOLS.ASM' ;------------------------------------------------------------- ; ; Подпрограммы, вызываемые из С-программ, для манипулирования ; метками томов в MS-DOS ; Замечание: эти подпрограммы изменяют адрес текущей DTA ; ;------------------------------------------------------------- ARG1 EQV [bp + 4] ;адресация стека DGROUP GROUP DATA _TEXT SEGMENT byte public 'CODE' ASSUME cs: TEXT, ds: DSKOUT ;--------------------------------------------------------- ; ; char *Get VolLabel ( ); /* возвращает указатель на имя метки ; тома */ ;--------------------------------------------------------- PUBLIC _GetVolLabel _GetVolLabel PROC near push bp mov bp,sp push si push di call SetDTA ;передать адрес DTA MS-DOS mov dx, offset DGROUP:ExtendedFCB mov ah,11h ;AH = регистр функции 11h int 21h ;прерывания 21h, поиск пер- test al,al ;вого входа jnz L01 ;найденная метка mov si,offset DGROUP:DTA mov di,offset DGROUP:VolLabel call CopyName mov ax,offset DGROUP:VolLabel jmp short L02 L01: xor ax,ax L02 pop di pop si pop bp ret _GetVolLabel ENDP ;--------------------------------------------------------- ; ; int RenameVolLabel( label ); /* возвращает код ошибки*/ ; char *label; /* указатель к новому име- ; ни метки тома */ ; ;--------------------------------------------------------- PUBLIC _RenameVolLabel _RenameVolLabel PROC near push bp mov bp,sp push si push di mov si,offset DGROUP:VolLabel ;DS:DX - старое имя тома mov di,offset DGROUP:Name1 call CopyName ;скопировать ста- ;рое имя в FCB mov si,ARG1 mov di,offset DGROUP:Name call CopyName ;скопировать но- ;вое имя в FCB mov dx,offset DGROUP:ExtendedFCB ; DS:DX -- FCB mov ah,17h ;AH = номер ;функции INT 21H int 21h ;переименовать xor ah,ah ;AX=00H(успех) или ; 0FFH(неудача) pop di ;восстановить ре- ;гистры и вернуться pop si pop bp ret _RenameVolLabel ENDP ;--------------------------------------------------------- ; ; int NewVolLabel( label ); /* возвращает код ошибки*/ ; char *label; /* указатель к новому име- ; ни метки тома */ ; ;--------------------------------------------------------- PUBLIC _NewVolLabel _NewVolLabel PROC near push bp mov bp,sp push si push di mov si,ARG1 mov di,offset DGROUP:Name1 call CopyName ;скопировать но- ;вое имя в FCB mov dx,offset DGROUP:ExtendedFCB ; mov ah,16h ;AH = номер ;функции INT 21H int 21h ;создать вход в ;каталог xor ah,ah ;AX=00H(успех) или ;0FFH(неудача) pop di ;восстановить ре- ;гистры и вернуться pop si pop bp ret _NewVolLabel ENDP ;--------------------------------------------------------- ; ; int DeleteVolLabel( label ); /* возвращает код ошибки*/ ; ;--------------------------------------------------------- PUBLIC _DeleteVolLabel _DeleteVolLabel PROC near push bp mov bp,sp push si push di mov si,offset DGROUP:VolLabel mov di,offset DGROUP:Name1 call CopyName ;скопировать те- ;кущее имя тома ;для FCB mov dx,offset DGROUP:ExtendedFCB ; mov ah,13h ;AH = номер ;функции INT 21H int 21h ;уничтожить вход ;каталога xor ah,ah ;AX=00H(успех) или ;0FFH(неудача) pop di ;восстановить ре- ;гистры и вернуться pop si pop bp ret _DeleteVolLabel ENDP ;--------------------------------------------------------- ; ; другие подпрограммы ; ;--------------------------------------------------------- SetDTA PROC near push ax ;защитить использу- ;емые регистры push dx mov dx,offset DGROUP:DTA ;DS:DX-DTA mov ah,1Ah ;AH = номер функции ; INT 21H int 21h ;установить DTA pop dx ;восстановить ре- ;гистры и вернуться pop ax ret SetDTA ENDP ;в вызывающей программе: CopyName PROC near ;si - исходное имя в ASCIIZ ;di - место для копирования push ds pop es ; ES = DGROUP mov cx,11 ; длина поля имени L11: lodsb ; скопировать новое ; имя в FCB test al,al jz L12 ; ..пока не достиг- ; нут пустой знак stosb loop L11 L12: mov al,' ' ;заполнить новое имя пробелами rep stosb ret CopyName ENDP _TEXT ENDS _DATA SEGMENT word public 'DATA' VolLabel DB 11 dup(0),0 ExtendedFCB DB 0FFH ;должно быть 0FFH ;для расширенного ;FCB DB 5 dup(0) ;(зарезервировано) DB 1000b ;байт атрибута (бит 3 = 1) ;идентификатор дисковода по DB 0 ;умолчанию Name1 DB 11 dup('7') ;глобальное шаблонное имя DB 5 dup(0) ;(неиспользованное) Name2 DB 11 dup(0) ;второе имя (для входа ;переименованния) DB 9 dup(0) ;(неиспользованное) DTA DB 64 dup(0) _DATA ENDS END Рисунок 8-8. Программы для управления метками томов Ричард Уилтон Глава 9. Управление памятью Персональные компьютеры, совместимые с MS-DOS, укомплектовываются максимум тремя типами памяти прямого доступа (ОЗУ): стандартной па- мятью, расширенной памятью (expanded memory) и дополнительной памятью (extended memory). Все ПЭВМ с определенной системой MS-DOS имеют, по крайней мере, стандартную память, однако маличие расширенной или дополнительной па- мяти зависит от особенностей аппаратуры и модели микропроцессора. Каж- дый класс памяти обладает своими возможностями, характеристиками и ог- раничениями. Одновременно для каждого класса памяти имеется своя тех- нология управления, что и является предметом данной главы. 1. Стандартная память Стандартная память - это память объема до 1 Мбайта, которая не- посредственно адресуется микропроцессором Intel 8086/8088 или микроп- роцессором 80286, либо микропроцессором 80386, работающем в режиме эмуляции микропроцессора 8086. Физические адреса для ссылок на стан- дартную память образуются из 16-разрядного регистра сегмента, который выступает как базовый регистр и содержит адрес параграфа, и 16-разряд- ного смещения, содержащегося в индексном регистре или в команде, под- лежащей исполнению. На ПЭВМ IBM PC и совместимых с ними, MS-DOS и программы, которые выполняются под ее управлением, занимают младшие 640 Кб или менее пространства стандартной памяти. Пространство памяти сверх 640 Кб раз- делено между кристаллами ПЗУ (постоянной памятью) на системной плате, которые содержат различные программы обслуживания устройств нижнего уровня и тестовые программы, и между кристаллами ОЗУ и ПЗУ на платах расширения, которые используются для буферов ввода-вывода и дополни- тельных программ, зависящих от устройств. Младшие 640 Кбайт памяти, обслуживаемые MS-DOS, разделены на три зоны (рис.9-1): - Таблица векторов прерываний; - Область операционной системы; - Область транзитных программ. -------------------------------¬ 100000H (1 Мбайт) ¦ ПЗУ BIOS ¦ ¦ дополнительные ПЗУ-программы ¦ ¦ на платах расширения, отобра-¦ ¦ жаемые в память буферы ввода/¦ ¦ вывода ¦ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ¦~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~¦ +------------------------------+ A0000H (640 Кбайт) ¦ ¦ ¦ Область, доступная ¦ ¦ прикладным программам ¦ ¦ (область транзитных программ)¦ +------------------------------+ Граница варьирует ¦ MS-DOS и ¦ ¦ системные буферы, таблицы ¦ ¦ и драйверы устройств ¦ +------------------------------+ 00400H (1 Кбайт) ¦ Таблица векторов прерываний ¦ L------------------------------- 00000H Рис.9-1. Схема, на которой показана стандартная память для IBM-PC-совместимых машин, работающих под управлением MS-DOS. Нижние 1024 байтов памяти используются в качест- ве таблицы векторов прерываний. Расположенная выше па- мять вплоть до границы в 640 сб доступна для MS-DOS и прикладных программ, выполняемых под ее управлением. Верхние 384 Кбайт используются BIOS ПЗУ, другими подп- рограммами диагностики и управления устройствами, а так- же буферами ввода-вывода. Таблица векторов прерываний занимает нижние 1024 байта памяти (адреса 0000-003FFH); ее адрес и длина "встроены" в процессор и не мо- гут быть изменены. Каждое двойное слово (одна позиция) в таблице назы- вается вектором прерывания и содержит номер сегмента и смещения подп- рограммы обработки прерывания для соответствующего аппаратного либо программного прерывания. Программы обработки прерываний обычно встраи- ваются в операцонную систему, однако в особых случаях прикладные прог- раммы могут использовать собственные подпрограммы обработки прерыва- ний. Вектор прерываний с номерами, которые не используются программны- ми функциями или некоторыми устройствами, обычно инициализируются опе- рационной системой указателем на команду возврата из прерываний либо на программу вывода сообщения об ошибке. Область операционной системы начинается сразу за таблицей векто- ров прерываний и содержит собственно операционную систему, ее таблицы и буфера, дополнительные загружаемые драйверы устройств, указанные в файле CONFIG.SYS, и резидентную часть командного интерпретатора COMMAND.COM. Занимаемый под область операционной системы объем памяти варьируется в зависимости от используемой версии MS-DOS, количества дисковых буферов, а также от количества и размера загружаемых драйве- ров устройств. Область транзитных программ (Transient program area - TPA) - это остаток ОЗУ выше области операционной системы, занимает все пространс- тво вплоть до границы в 640 Кбайт или до конца установленного ОЗУ (ес- ли объем ОЗУ менее 640 Кбайт). Внешние команды MS-DOS (такие как CHKDSK), другие программы загружаются для исполнения в области тран- зитных программ. Транзитная часть COMMAND.COM также загружается в эту область. Область транзитных программ имеет структуру, именуемую полем па- мяти, которая подразделяется на участки, называемые, в свою очередь, входами поля памяти (или блоками памяти). Эти входы образуются из мно- жества параграфов (16 байт); минимальный размер входа равен длине па- раграфа, а максимальный - длине области транзитных программ. Перед каждым входом поля памяти располагается управляющий блок, называемый заголовком входа. Он содер- жит информацию, в которой указаны длина и состояние входа поля памяти. MS-DOS анализирует заголовки входов памяти, когда вызывается фун- кция с требованием по распределению блока памяти, его модификации или освобождению; когда загружается и исполняется программа с помощью фун- кции EXEC (прерывание 214, функция 4BH); или когда программа заверша- ется. Если некоторый заголовок входа памяти оказывается запорченным, MS-DOS возвращает вызывающему процессу сообщение об ошибке. Если этим процессом является COMMAND.COM, тогда им выдается сообщение "Ошибка распределения памяти" (Memory allocation error) и система останавлива- ется. 1.1. Управление стандартной памятью со стороны MS-DOS Ядро MS-DOS поддерживает три функции управления памятью, активи- зируемые посредством прерывания 21H, которые воздействуют на область транзитных программ: - функция 48H (Распределить блок памяти); - функция 49H (Освободить блок памяти); - функция 4AH (Переопределить блок памяти). Эти три функции (табл. 9-1) могут быть вызваны прикладными прог- раммами, командным процессором, или непосредственно MS-DOS для динами- ческого выделения, переопределения и освобождения входов полей памяти по мере необходимости. См.: "Системные вызовы: Прерывание 21H; Функции 48H, 49H, 4AH". Табл. 9-1. Функции управления памятью MS-DOS. ------------------------------------------------------------------- ¦ Наименование функции ¦ Параметры вызова ¦ Возврат ¦ ¦----------------------¦------------------¦-------------------------¦ ¦ Распределить ¦ AH = 48H ¦ AX = адрес сегмента ¦ ¦ (выделить) блок ¦ BX = количество ¦ выделенного блока ¦ ¦ памяти ¦ требуемых ¦ В случае ошибки: ¦ ¦ ¦ параграфов ¦ BX = размер максимально ¦ ¦ ¦ ¦ доступного блока ¦ ¦ ¦ ¦ в параграфах ¦ ¦----------------------¦------------------¦-------------------------¦ ¦ Освободить блок ¦ AH = 49H ¦ нет ¦ ¦ памяти ¦ ES = адрес сег- ¦ ¦ ¦ ¦ мента осво- ¦ ¦ ¦ ¦ бождаемого ¦ ¦ ¦ ¦ блока ¦ ¦ ¦----------------------¦------------------¦-------------------------¦ ¦ Переопределить ¦ AH = 4AH ¦ В случае ошибки: ¦ ¦(выделенный) блок ¦ BX = новый размер¦ BX = максимальный размер¦ ¦ памяти ¦ блока в ¦ блока в параграфах ¦ ¦ ¦ параграфах ¦ ¦ ¦ ¦ ES = адрес сег- ¦ ¦ ¦ ¦ мента перео-¦ ¦ ¦ ¦ пределяемого¦ ¦ ¦ ¦ блока ¦ ¦ ¦----------------------¦------------------¦-------------------------¦ ¦ Прочитать/установить ¦ AH = 58H ¦ В случае чтения ¦ ¦ дисциплину ¦ AL = 00H (прочи- ¦ дисциплины: ¦ ¦ распределения *) ¦ тать вариант¦ AX = код дисциплины ¦ ¦ ¦ дисциплины) ¦ ¦ ¦ ¦ = 01H (устано-¦ ¦ ¦ ¦ вить дисцип-¦ ¦ ¦ ¦ лину) ¦ ¦ ¦ ¦ В случае ¦ ¦ ¦ ¦ установки: ¦ ¦ ¦ ¦ BX= дисциплина: ¦ ¦ ¦ ¦ 00H = первый ¦ ¦ ¦ ¦ подходящий ¦ ¦ ¦ ¦ 01H = наилучший ¦ ¦ ¦ ¦ подходящий ¦ ¦ ¦ *) Tолько для версии ¦ 02H = последний ¦ ¦ ¦ 3.x MS-DOS ¦ подходящий ¦ ¦ ------------------------------------------------------------------- Когда ядро MS-DOS получает запрос на выделение памяти, выполняет- ся просмотр стека заголовков входов памяти с целью отыскания свободно- го входа, удовлетворяющего параметрам запроса. Супервизор памяти может использовать любую из трех дисциплин выделения блоков памяти: - Первый подходящий - вход поля памяти представляет собой наи- меньший адрес, с которого может быть выделен достаточный для удовлет- ворения запроса блок памяти. - Наилучший подходящий - наименьший блок памяти, удовлетворяющий запросу, независимо от месторасположения блока. - Последний подходящий - вход поля памяти представляет собой наи- высший адрес, начиная с которого может быть выделен достаточный для удовлетворения запроса блок памяти. Если выбранный вход поля памяти превышает необходимый размер бло- ка памяти, вход поля делится на две части таким образом, что программа получает в точности столько, сколько требовалось. Для остатка первона- чального входа поля памяти формируется новый заголовок; он помечается как "свободный" и может быть использован для удовлетворения последую- щих запросов на выделение памяти. Исследование дисциплин распределения памяти показывают, что вари- ант "первого подходящего" является наиболее эффективным, и он предс- тавляет собой умолчание для MS-DOS. Однако, в версиях MS-DOS 3,0 и старше, прикладные программы могут выбрать различные дисциплины управ- ления памятью с помощью прерывания 21H, функции 58H (Прочитать/устано- вить дисциплину распредеения). См.: "Системные вызовы, Прерывания 21H, функция 58H". 1.2. Использование функций управления памятью Когда программа начинает исполняться, она уже обладает двумя вхо- дами поля памяти, распределенными "от имени" MS-DOS функцией EXEC (Прерывание 21H, функция 4BH). Первый вход содержит среду программы и соответствует длине этой информации; второй вход (называемый в главе программным блоком) содержит PSP, код программы, данные и стек. Размер памяти, выделяемый MS-DOS программному блоку для подзагру- жаемой тразитной программы, зависит от ее типа (.COM или .EXE). Обычно под программу типа .COM выделяется целиком первый из входов поля памя- ти, достаточный для размещения файла программы, плюс 256 байт для PSP и, по крайней мере, 2 байта под стек. Поскольку область транзитных программ случайным образом фрагментируется и имеет более одного входа перед загрузкой программы, программа типа .COM обычно ограничена всей доступной памятью, за исключением принадлежащего операционной системе пространства - память разделяется между сравнительно небольшой об- ластью среды и сравнительно большим программным блоком. С другой стороны, количество памяти, выделяемое под программы ти- па .EXE, контролируется двумя полями памяти, именуемыми MINALLOC и MAXALLOC в заголовке файла программы .EXE. Поле MINALLOC указывает загрузчику MS-DOS на дополнительный размер памяти в параграфах по от- ношению к коду и данным, находящимся в файле. Эта память должна быть доступна для исполнения программы. Поле MAXALLOC содержит максимальное количество дополнительных параграфов, которые могут быть выделены программе, в случае наличия достаточного количества памяти. Установленное по умолчанию редактором связей фирмы Мicrosoft зна- чения поля MAXALLOC составляет FFFFH параграфов, что соответствует 1 Мбайту. Поэтому, обычно для программ типа .EXE выделяется при загрузке вся доступная память, как и в случае файла программы типа .COM. И хотя имеется возможность установить значение поля MAXALLOC иным, меньшим, с помощью переключателя CPAR MAXALLOC или утилиты EXEMOD, поставляемой вместе с компиляторами фирмы Microsoft, лишь немногие программисты этим пользуются. Короче говоря, когда программа начинает исполняться, она обычно владеет всей доступной памятью - чаще всего этой памяти даже больше, чем реально необходимо. Если же программа рассчитана на "хороший стиль" управления памятью, в частности, загружает подпроцессы, она должна немедленно освободить всю лишнюю память. В программах на Ассем- блере лишняя память освобождается с помощью прерывания 21H, функция 4AH (Переопределить блок памяти), с указанием адреса сегмента PSP программы в регистре ES и количества парагафов памяти, оставляемых прогамме для использования, в регистре BX. (См.рис.9-2 и 9-3). В боль- шинстве языков программирования высокого уровня, таких, например, как язык Си фирмы Microsoft, лишняя память освобождается модулем раскрутки библиотеки времени исполнения. . . . _TEXT segment para public 'CODE' org 100h assume cs:_TEXT,ds:_TEXT,es:_TEXT,ss:_TEXT main proc near ;точка входа из MS-DOS ; CS = DS = ES = SS = PSP mov sp,offset stk;сначала пересылаем стек для сохранения ;сейчас освобождаем лишнюю память mov bx,offset stk;количество оставляемых параграфов mov cl,4 ;(разделить смещение конца программы на 16 shr bx,cl ; и округлить) inc bx mov ah,4ah ;Функция 4AH = переопределить блок памяти int 21h ;передать управление MS-DOS jc error ;переход в случае ошибки переопределения . ; блока . ;иначе продолжить работу . main endp . . . dw 64 dup (?) stk equ $ ;база нового сегмента стека _TEXT ends end main ;определение точки входа программы Рис.9-2. Пример программы типа .COM, освобождающей лишнюю память после получения управления из MS-DOS. Прерывание 21H, функция 4AH вызывается с указанием сегментного адреса PSP программы в ре- гистре ES и количества параграфов памяти в качестве остатка - в регистре BX. _TEXT segment word public 'CODE' ;исполняемый сегмент кода assume cs:_TEXT,ds:_DATA,ss:STACK main proc far ;точка входа из MS-DOS ;CS = сегмент _TEXT ;DS = ES = PSP mov ax,_DATA ;установить DS= выходной сегмент данныx mov ds,ax ;возвратить лишнюю память mov ax,es ;Пусть AX= сегментный адрес базы PSP, mov bx,ss ;а BX = сегментный адрес базы стека aub bx,ax ;зарезервировать сегм.стека - сегм.PSP add bx,stksize/16 ;плюс количество параграфов стека и inc bx ;округлить mov ah,4ah ;Функция 4AH=переопределить блок памяти int 21h ;передать управление МS-DOS jc error ;перейти, если переопределение неудачное . . . main endp _TEXT ends _DATA segment word public 'DATA' ;статические и динамические переменные . . . _DATA ends STACK segment para stack 'STACK' db stksize dup (?) STACK ends end main ;определить точку входа программы Рис.9-3. Пример программы типа .EXE, освобождающей лишнюю память после получения управления из MS-DOS. Данный код зависит от указан- ного порядка сегментов. Когда программа .EXE вызывается из различных объектных модулей, требуется уже иная техника для определения количества памяти, занимаемой программой во время счета. В дальнейшем, если транзитной программе потребуется дополнитель- ная память для буферов, таблиц или для других целей, она может вызвать прерывание 21H, функция 48H (Распределить блок памяти) с указанием не- обходимого количества параграфов. Если в памяти имеется блок достаточ- ного размера, MS-DOS формирует новый вход поля памяти заданной длины и возвращает указатель на его базу в виде сегментного адреса в регистре AX. Если вход поля памяти требуемого размера не может быть создан, MS-DOS возвращает в регистре AX ход ошибки, а в регистре BX размер в параграфах наибольшего свободного блока памяти. Прикладная программа может проанализировать это значение для выяснения возможности продол- жения счета при меньшем количестве доступной памяти. Когда программа завершает использование выделенной ей памяти, она может вызвать прерывание 21H, функция 49H, для освобождения этой памя- ти. Это позволяет MS-DOS собрать малые блоки свободной памяти в непре- рывные входы поля памяти и уменьшает вероятность неудачи из-за фраг- ментации при выдаче последующих запросов на распределение памяти; все входы поля памяти, которыми владеет программа освобождаются в момент завершения программы по прерываниям 20H либо 21H, функции 00H либо 4CH. На рис.9-4 показан костяк программы, демонстрирующей использова- ние средств динамического распределения памяти. . . . mov bx,800h ;800H параграфов = 32 Кбайт mov ah,48h ;Функция 48H = выделить блок int 21h ;передать управление MS-DOS jc error ;перейти в случае ошибки при распределении mov bufseg,ax ;сохранить сегментный адрес блока ;открыть рабочий файл ... mov dx,offset file1 ;DS:DX = адрес имени файла mov ax,3d00h ;Функция 3DH=открыть только на чтение int 21h ;передать управление MS-DOS jc error ;перейти в случае ошибки открытия файла mov handle1,ax ;сохранить идентификатор рабочего файла ;создать файл дублирования mov dx,offset file2 ;DS:DX = адрес имени файла mov cx,0 ;CX = атрибут (обычный) mov ah,3ch ;Функция 3CH = создать файл int 21h ;передать управление MS-DOS jc error ;перейти, если неудача при создании файла mov handle2,ax ;сохранить идентификатор файла ; дублирования push ds ;установить ES = сегмент выходных данных pop es mov ds,bufseg ;установить DS:DX = выделенный блок xor dx,dx assume ds:NOTHING,es:_DATA;сообщить ассемблеру next: ;читать рабочий файл ... mov bx,handle1 ;идентификатор рабочего файла mov cx,8000h ;попытка прочитать 32 Кбайт mov ah,3fh ;Функция 3FH =читать int 21h ;передать управление MS-DOS jc error ;перейти, если неудача пр чтении or ax,ax ;достигнут конец файла? jz done ;да, выйти из этого цикла ;а сейчас пишем в файл дублирования mov cx,ax ;установить длину записываемых дан- ;ных = длине читаемых данных mov bx,handle2 ;идентификатор файла дублирования mov ah,40h ;функция 40H = писать int 21h ;передать управление MS-DOS jc error ;перейти в случае ошибки записи cmp ax,cx ;запись завершена? jne error ;нет, вероятно, диск переполнен jmp next ;переслать очередную запись done: push es ;восстановить DS = сегмент данных pop ds assume ds:_DATA,es:NOTHING;сообщить ассемблеру ;освободить выделенный блок.. mov es,bufseg ;базовый адрес сегмента блока mov ah,49h ;Функция 49H = освободить блок int 21h ;передать управление MS-DOS jc error ;(никогда не приводит к ошибке) ;сейчас закрыть файл дублирования mov bx,handle2 ;идентификатор файла дублирования mov ah,3eh ;функция 3EH = закрыть int 21h ;вызов MS-DOS jc error ;перейти,если неудача при закрытии . . . file1 db 'MYFILE.DAT',0 ;имя рабочего файла file2 db 'MYFILE.BAK',0 ;имя файла дублирования handle1 dw ? ;идентификатор рабочего файла handle2 dw ? ;идентификатор файла дублирования bufseg dw ? ;сегментный адрес выделенного блока Рис. 9-4. Схематический пример динамического распределения памяти. Программа запрашивает блок памяти в 32 Кбайт, используя его для копирования в файл дублирования, а затем освобождает блок памяти. Обратите внимание на директивы ассемблера ASSUME, которые приводят к генерации правильных ссылок на переменные, содержащие идентификаторы файлов. 2. Расширенная память (Expanded memory) Первоначально версия 3.0 Expanded Memory Specification (EMS) - Спецификация расширенной памяти - была разработана совместными усилия- ми Lotus Development Corporation и Intel Corporation и предложена на Весенней ярмарке COMDEX в 1985 г. EMS спроектирована с целью обеспече- ния прикладных программ, работающих на ПЭВМ на базе микропроцессоров 8086/8088, либо на ПЭВМ на базе микропроцессоров 80286/80386 (в режиме эмуляции), унифицированным средством для обхода ограничения размера стандартной памяти в 1 Мбайт, предоставляя, таким образом, этим прог- раммам значительно большее количество памяти с быстрым прямым досту- пом. Версия EMS 3.2, модифицированная по отношению к версии 3.0., за счет поддержки многозаданных операционных систем, была выпущена совсем недавно как плод совместных усилий фирм Lotus, Intel и Microsoft. EMS представляет собой с функциональной точки зрения подсистему переключаемых банков памяти; она состоит из устанавливаемых пользова- телем плат на шину расширения IBM PC и резидентного драйвера, называе- мого Expanded Memory Manager (EMM) - программа управления расширенной памятью - и поставляемого изготовителем плат. На одну ПЭВМ может уста- навливаться до 8 Мбайт расширенной памяти. Расширенная память доступна прикладному программному обеспечению страницами по 16 Кбайт в каждой, которые отображаются EMM в непрерывную область размером в 64 Кбайт, называемую кадром страниц, где-нибудь выше стандартной памяти, которая используется MS-DOS (0 - 640 Кбайт). В результате прикладная программа получает одновременный доступ не более чем к 4 страницам расширенной памяти размером в 16 Кбайт каждая. Расположение кадра страниц опреде- ляется пользователем таким образом, чтобы избежать конфликтов при ад- ресации с другими элементами оборудования. 2.1. Программа управления расширенной памятью (EMM) Программа управления расширенной памятью (EMM) обеспечивает неза- висимый от оборудования интерфейс между прикладными программами и пла- тами расширенной памяти. EMM поставляется производителем плат в виде загружаемого драйвера символьного устройства и подключается к MS-DOS с помощью команды DEVICE, указываемой в файле CONFIG.SYS на системным диске. Структурно EMM подразделяется на две независимые компоненты, ко- торые относятся к драйверу и программе управления. Драйверная компо- нента имитирует некоторые действия обычных загружаемых драйверов уст- ройств в том, что она включает подфункции инициализации и выходного состояния, а также правильный заголовок устройства. См. "Программиро- вание в среде MS-DOS: Настройка MS-DOS: Загружаемые драйверы устрой- ств". Второй, главный элемент EMM, является фактическим интерфейсом между прикладным программным обеспечением и оборудованием расширенной памяти. Обеспечиваются несколько классов услуг: - определение состояния подсистемы расширенной памяти; - выделения страниц расширенной памяти; - отображения логических страниц на физическую память; - освобождение страниц расширенной памяти; -------------------¬ 8 Мбайт +------------------+ +------------------+ +------------------+ +------------------+ +------------------+ 1 Мбайт -------------------¬ /+------------------+ ¦ ¦ / ~~~~~~~~~~~~~~~~~~ ¦ ПЗУ BIOS и др. ¦ / ¦~~~~~~~~~~~~~~~~~~¦ ¦ ¦ / /+------------------+ +------------------+ / / +------------------+ кадр ¦ ¦ / / +------------------+ страни- +------------------+/ / +------------------+ цы EMS /+------------------+/ +------------------+ (4 стр{ +------------------+_ _ _ _ _ _ _+------------------+ по 16 \+------------------+ +------------------+ Кбайт) +------------------+\ +------------------+ ¦ ¦ \ +------------------+ 640 +------------------+ \ +------------------+ Кбайт ¦ ¦ \ +------------------+ ¦ Область ¦ \ +------------------+ ¦ прикладных ¦ \ +------------------+ ¦ программ ¦ \+------------------+ +------------------+ +------------------+ ¦ ¦ +------------------+ ¦ MS-DOS ¦ +------------------+ ¦ ¦ +------------------+ 00400H +------------------+ +------------------+ (1Кбайт)¦ Таблица векторов ¦ +------------------+ ¦ прерываний ¦ +------------------+ 00000H L------------------- L------------------- Рис.9-5. Схема соответствия расширенной и стандартной памяти: страницы расширенной памяти размером в 16 Кбайт отображаются на об- ласть длиной в 64 Кбайт, называемую кадром страниц и располо- женную выше границы в 640 Кбайт. Расположение кадра страниц выбирается пользователем таким образом, чтобы избежать конф- ликтов в адресации между ПЗУ или буферами ввода-вывода на платах расширения. - поддержка многозадачных операционных систем; - диагностические подпрограммы. Прикладные программы взаимодействуют с EMM посредством программ- ного прерывания (Прерывание 67H). Ядро MS-DOS не участвует в управле- нии расширенной памятью и не использует ее в собственных целях. 2.1.1. Проверка наличия расширенной памяти Перед попыткой использовать средства расширенной памяти приклад- ная программа должна установить наличие EMM, а затем - обратиться к EMM для проверки состояния собственно плат памяти. Имеются два способа программного тестирования наличия EMM. Первый метод заключается в вызове функции "Открыть файл или уст- ройство" (Прерывание 21H, Функция 3DH), используя обязательное имя ус- тройства драйвера EMM: EMMXXXX0. В случае успешного завершения функции "Открыть", фиксируется одно из двух условий - либо драйвер существует, либо файл с данным именем содержится в текущем справочнике на умалчи- ваемом дисковом устройстве. Для исключения последней возможности, программа должна вызвать функции IOCTL "Получить информацию об устрой- стве" (Прерывание 21H, функция 44H, подфункция 00H) и "Проверить вы- ходное состояние" (Прерывание 21H, функция 44H, подфункция 07H) для определения того, связан ли возвращаемый операцией "Открыть" идентифи- катор файла с устройством. В случае файла идентификатор, возвращаемый из функции "Открыть", должен быть закрыт (Прерывание 21H, функция 3EH) с тем, чтобы его переиспользовать для иного файла или устройства. Второй способ тестирования наличия драйвера - использовать адрес, содержащийся в векторе прерываний 67H, для анализа заголовка драйвера предполагаемого EMM. (Содержимое вектора прерываний может быть получе- но в удобной форме с помощью прерывания 21H, функция 35H). Если EMM уже существует, поле имени по смещению 0AH в заголовке драйвера, со- держит строку EMMXXXX0. Этот метод достаточно хорошо защищен "от дура- ка" и более предпочтителен по сравнению с функцией "Открыть". Однако, указанный метод имеет незначительный недостаток, связанный с необходи- мостью анализа области памяти, которая не принадлежит прикладной прог- рамме. Оба метода проверки наличия EMM показаны на рис.9-6 и 9-7. . . . ;попытка "открыть" EMM mov dx,seg emm_name ;DS:DX = адрес имени mov ds,dx ;EMM mov dx,offset emm_name mov ax,3d00h ;функция 3DH, режим 00H ;= открыть, только для чтения int 21h ;передача управления MS-DOS jc error ;перейти, в случае неудачи открытия ;Функция "открыть" завершилась успешно ;убедиться, что это не файл... mov bx,ax ;BX = идентификатор, который открыт mov ax,4400h ;функция 44H, подфункция 00H ;=IOCTL "Получить информацию об устройстве int 21h ;передать управление MS-DOS jc error ;перейти, если вызов IOCTL неудачный and dx,80h ;бит 7=1 для символьного устройства jz error ;перейти, если файл ;EMM существует; проверить ;его доступность... ;(BX по-прежнему содержит идентификатор) mov ax,4407h ;Функция 44H, подфункция 07H ;= IOCTL"Получить выходное состояние" int 21h ;передать управление MS-DOS jc error ;перейти, если вызов IOCTL неудачен or al,al ;проверить состояние устройства jz error ;если AL=0, EMM не доступна ;а сейчас закроем идентификатор... ;(BX по-прежнему содержит идентификатор) mov ah,3eh ;Функция 3EH = Закрыть int 21h ;передать управление MS-DOS jc error ;перейти, если закрытие с ошибкой . . . emm_name db 'EMMXXXX0',0 ;обязательное имя устройства для EMM Рис.9-6. Проверка наличия программы управления расширенной памятью (EMM) с помощью функций MS-DOS "Oткрыть файл или устройство" (Прерывание 21H, функция 3DH) и IOCTL (Прерывание 21H, функ- ция 44H). emm_int equ 67h ;программное прерывание EMM . . . ;вначале получить содержимое ;вектора прерывания EMM mov al,emm_int ;AL = номер прерывания EMM mov ah,35h ;Функция 35H - получить вектор int 21h ;передать управление MS-DOS ;сейчас ES:BX = адрес идентификатора ;предположим, что ES: 0000 указывает ;на базу EMM mov di,10 ;ES:DI = адрес поля имени ;в заголовке драйвера mov si,seg emm_name ;DS:SI = адрес ожидаемого mov ds,si ;имени драйвера EMM mov si,offset emm_name mov cx,8 ;длина поля имени cld repz cmpsb ;сравнить имена... jnz error ;переход, если нет драйвера . . . emm_name db 'EMMXXXX0',0 ;обязательное имя устройства для EMM Рис.9-7. Проверка наличия программы управления расширенной памятью (EMM) путем анализа поля имени в заголовке драйвера устройст- ва. 2.1.2. Использование расширенной памяти После определения факта наличия EMM, прикладная программа должна "обходить" MS-DOS и взаимодействовать с EMM непосредственно посредст- вом программного прерывания 67H. Последовательность вызова такова: mov ah,function ;в AH - функция EMM . ;Загрузить остальные регистры . ;необхдимыми значениями . ;для запроса int 67h ;передать управление EMM Обычно регистры ES:DI используются для передачи адреса буфера или массива, а регистр DX используется для хранения системного идентифика- тора расширенной памяти. Некоторые функции EMM используют также другие регистры (в основном AL и BX) для передачи такой информации, как номе- ра физических и логических страниц. В таблице 9-2 приведены функции EMM. После возврата из вызова функции EMM, регистр AH содержит нуль, если функция вызвана успешно; в противом случае в регистре AH содер- жится код ошибки с установленным битом, отвечающем наиболее значитель- ной ошибке (табл.9-3). Остальные значения обычно возвращаются в регис- трах AL и BX или в буфере пользователя. Табл. 9-2. Сводная таблица программного интерфейса между приложением и спецификацией расширенной памяти, обеспечиваемого EMM *) ---------------------------------------------------------------------- Имя функ- ¦ Действие ¦Обраще-¦Возвращает¦ Комментарии ции ¦ ¦ ние ¦ ¦ ---------------------------------------------------------------------- Получить Тест функциональ- Эта функция использу состояние ности программно- AH=40H AH=состоя- ется после установки программы го и аппаратного ние программы, в рамках од- управления обеспечения рас- ной из технологий опре- ширенной памяти деления наличия EMM, представленных на Рис. 9-6 и 9-7 ---------------------------------------------------------------------- Получить Получить адрес AH=состоя- Кадр страницы поде- сегмент сегмента кадра AH=41H ние лен на 4 16-Кбайтные кадра страницы EMM BX=сегмент страницы, использующие- страницы кадра ся для отображения ло- страни- гических страниц расши- цы,если ренной памяти в физи- AH=00H ческую память процессо- ра 8086/8088 ---------------------------------------------------------------------- Получить Получить число AH=состоя- Для использования страницы активных логичес- ние этой функции прикладная расширен- ких страниц рас- AH=42H BX=число программа не нуждается ной памя- ширенной памяти неразме-уже в запросе об иден- ти и число еще не щенных тификаторе EMM распределенных страниц страниц EMM,если AH=00H DX=общее число страниц EMM в системе ---------------------------------------------------------------------- Распреде- Получить иденти- AH=43H AH=состо- Эта функция эквивален лить рас- фикатор EMM и BX=раз- яние тна функции открытия ширенную разместить логи- мещае- DX=иденти- файла для EMM.Возвращае память ческие страницы, мые ло- фикатор мый идентификатор анало управляемые по гичес- если гичен идентификатору этому идентифи- кие AH=00H файла и соответствует катору стра- определенному числу стр ницы аниц EMM. Этот идентифи катор должен использо- ваться с каждым последу ющим запросом на отобра жение памяти и должен быть освобожден операци ей закрытия при заверше нии прикладной програм- мы. Выполнение этой функ ции может быть неуспеш- ным из-за того, что пре вышено количество имею- щихся идентификаторов EMM или страниц EMM.Для определения числа имею- щихся страниц можно вы- звать функцию 42H. ---------------------------------------------------------------------- Отображе- Отобразить одну AH=44H AH=состо- Число логических стр ние памя- из логических AL=физи- яние аниц должно быть в пре- ти страниц расши- чес- делах 0,...n-1, где n - ренной памяти, кая число логических стра- соответствующую стра- ниц, назначенные ранее некоторому иден- ница идентификатору EMM функ тификатору, на (0-3) цией 43H. одну из четырех BX=логи- Чтобы получить дос- физических стра- чес- туп к памяти после ее ниц из странич- кая отображения на физичес- ного кадра EMM стра- кую страницу,прикладной ница программе также нужен (0,... сегмент кадра страниц n-1) EMM,который можно полу- DX=иден- чить функцией 41H. тифи- катор EMM ---------------------------------------------------------------------- Освобо- Отменить распре- AH=45H AH=состо- Эта функция эквива- дить иден- деление логичес- DX=иден- яние лентна операции закры- тификатор ких страниц рас- тифи- тия файла.Она сообщает и память ширенной памяти, катор EMM,что прикладная прог назначенных не- EMM рамма не будет в даль- нейшем использовать дан которому иденти- ные, которые она могла фикатору,и затем хранить на страницах освободить сам расширенной памяти. этот идентифика- тор для новых назначений ---------------------------------------------------------------------- Получить Возвратить номер AH=состо- Возвращаемое значе- версию версии программ- AH=46H яние ние - это версия EMM, с EMM ного обеспечения AL=версия которой сочетается драй EMM EMM, вер. Номер версии зако- если дирован в виде BCD, где AH=00H целая часть размещена в старших 4 битах, а дроб ная - в младших 4 битах ---------------------------------------------------------------------- Сохранить Сохранить содер- AH=47H AH=состо- Эта функция предназ- контекст жимое регистров DX=иден- яние начена для использова- отображе- отображения стра- тифи- ния ее программами обра ния ниц расширенной катор ботки прерываний и рези памяти,находящих- EMM дентными драйверами или ся на платах рас- утилитами,которые долж- ширенной памяти, ны обращаться к расши- связывая их со- ренной памяти.Идентифи- держимое с кон- катор,используемый этой кретным идентифи- функцией,- это идентифи катором EMM катор, назначенный про- грамме обработки преры- ваний при ее инициализа ции, а не программе, ко торая была прервана ---------------------------------------------------------------------- Восстано- Восстановить со- AH=48H AH=состо- Использованию этой вить кон- держимое всех ап-DX=иден- яние функции должно предшест текст ото- паратных регист- тифи- вовать обращение к функ бражения ров отображения катор ции EMM 47H. Она позво- страниц расширен- EMM ляет программе обработ- ной памяти, свя- ки прерываний или рези- занное с данным дентному драйверу, ис- идентификатором пользующим расширенную память,восстановить кон текст отображения в том его состоянии, которое было в точке прерывания ---------------------------------------------------------------------- Получить Возвращает коли- AH=4BH AH=состо- Если возвращаемое число чество активных яние число идентификаторов = идентифи- идентификаторов BX=число нулю,то расширенная па- каторов EMM иден- мять не используется. EMM тифи- Количество активных като- идентификаторов EMM не ров должно превосходить 255 EMM, Одна программа может если сделать несколько запро AH=00H сов на распределение па мяти и, таким образом, управлять несколькими идентификаторами EMM. ---------------------------------------------------------------------- Получить Возвращает коли- AH=4CH AH=состо- Возвращаемое количес страницы, чество логичес- DX=иден- яние тво страниц при успеш- назначен- ких страниц рас- тифи-BX=число ном завершении функции ные иден- ширенной памяти, катор логи- всегда находится в пре- тификато- назначенных ука- EMM ческих делах 1 - 512.Количест- ру занному иденти- стра- во назначаемых любому фикатору ниц, идентификатору EMM стра если ниц памяти никогда не AH=00H равно нулю. ---------------------------------------------------------------------- Получить Возвращает мас- AH=4DH AH=состо- Массив заполняется страницы сив, в котором DI=об- яние элементами размером два для всех содержатся все ласть BX=коли- слова.Первое слово каж- идентифи- активные иденти- масси- чество дого элемента содержит каторов фикаторы и коли- ва для актив- идентификатор,второе со чество логичес- полу- ных держит число страниц, ких страниц рас- чения иденти- связанных с этим иденти ширенной памяти, инфор- фикато- фикатором.Значение,воз- связанных с каж- мации ров EMM вращаемое в BX указыва- дым идентифика- ES=сег- Если AH= ет число заполненных тором мент =00H,мас- двухсловных элементов масси- сив запол-массива. ва няется Поскольку максималь- так, как ное количество идентифи описано в каторов EMM равно 255, колонке размер массива не дол- "Коммен- жен превосходить 1020 тарии" байт. ---------------------------------------------------------------------- Получить/ Сохранить или AH=4EH AH=состо- Подфункции: устано- установить со- AL=номер яние 00H=получить содержимое вить ото- держимое регист- под- AL=байты регистров отображе- бражение ров отображения функ- в мас- ния в массив страницы страниц EMM на ции сиве 01H=установить значения плате расширен- DS:SI= отобра- регистров отображе- ной памяти массив, жения ния из массива содер- страниц 02H=получить и устано- жащий (под- вить значения реги- инфор- функция стров отображения в мацию 03H) одной операции об ото- 03H=возвратить размер браже- Массив с массива отображения нии указателем страниц (под- ES:DI полу- функции чает ин- Эта функция была до 01H,02H) формацию бавлена в версии 3.2 ES:DI= об отобра-EMM и предназначена для массив жении для поддержки многозадачно- для по- подфунк- го режима.В обычных слу лучения ций 00H чаях ее не следует ис- инфор- и 02H пользовать в прикладных мации программах. (под- Содержимое массива функции зависит от программного 00H,02H) и аппаратного обеспече- ния EMM.Помимо содержи- мого регистров отображе ния страниц,массив дол- жен содержать и другую информацию, которая не- обходима для восстанов- ления подсистемы расши- ренной памяти в ее преж нем состоянии ---------------------------------------------------------------------- *) Функции EMS 49H и 4AH (не помещенные в настоящий перечень) были оп- ределены в версии 3.0 EMM и были "зарезервированы" в более поздних версиях EMS. Табл. 9-3. Коды ошибок программы управления расширенной памятью (EMM) ---------------------------------------------------------------------- Код ошибки ¦ Значение ---------------------------------------------------------------------- 00H Функция завершилась успешно. 80H Внутренняя ошибка в программном обеспечении EMM. Возможна ошибка внутри драйвера или запорчен код в памяти. 81H Неисправность оборудования расширенной памяти 82H EMM занята 83H Неправильный системный идентификатор расширенной памяти 84H Функция, запрашиваемая приложением, не поддерживается EMM 85H Нет доступных системных идентификаторов расширенной памяти 86H Ошибка сохранения или восстановления контекста отображения 87H В запросе на распределения указано больше логических стра- ниц, чем доступно в системе; ни одна страница не выде- ляется. 88H В запросе указывается больше логических страниц, чем до- ступно в системе в текущий момент времени (запрос не превышает количество физических страниц, однако некото- рые страницы уже приписаны другим системным идентифика- торам); ни одна страница не выделяется. 89H Не может быть выделено нулевое количество страниц 8AH Запрашиваемая для отображения логическая страница лежит вне диапазона страниц, связанного с системным идентифи- катором 8BH Недействительный номер физической страницы в запросе ото- бражения (не в диапазоне 0-3). 8CH Область сохранения для контекста отображения переполнена 8DH Сохранение контекста отображения завершилось неудачно, по- скольку область сохранения уже содержит контекст, свя- занный с указанным системным идентификатором 8EH Восстановление контекста отображения завершилось неудачно, поскольку область сохранения не содержит контекст, свя- занный с указанным системным идентификатором 8FH Параметр подфункции не определен --------------------------------------------------------------------- Прикладная программа, использующая расширенную память, рассматри- вает эту память как системный ресурс, такой как файл или устройство, и обращается только к документированным функциям EMM по распределению, доступу и освобождению страниц расширенной памяти. Ниже указано общее правило, которого может придерживаться такая программа: 1. Установить наличие EMM одним из двух методов, показанных на рис.9-6 и 9-7. 2. После признания факта наличия драйвера, проверить его рабочее состояние функцией EMM 40H. 3. Проверить номер версии EMM с помощью функции EMM 46H с целью удостовериться, что все функции, запрашиваемые приложением, будут дос- тупны. 4. Получить сегментный адрес кадра страниц, используемого EMM, с помощью функции 41H. 5. Распределить требуемое количество страниц расширенной памяти с помощью функции 43H. В случае, если распределение завершилось успешно, EMM возвращает в регистре DX системный идентификатор, который будет использоваться приложением для ссылки на приписанные ему страницы па- мяти. Этот шаг является прямым аналогом открытия файла и применения системного идентификатора в последовательности операций чтения-записи над файлом. 6. Если требуемое количество страниц расширенной памяти отсут- ствует, следует запросить сведения о фактическом количестве доступных страниц (функция EMM 42H) и оценить возможность продолжения работы программы. 7. После успешного распределения необходимого количества страниц расширенной памяти, следует вызвать функцию EMM 44H с целью отображе- ния логических страниц в физический кадр страницы для сохранения дан- ных в расширенной памяти и извлечение их оттуда. 8. По завершении использования страниц расширенной памяти, осво- бодите их с помощью функции EMM 45H. В противном случае страницы не будут доступны для использования другими программами до очередного пе- резапуска системы. На рис.9-8 показана схема программы, иллюстрирующая этот подход по использованию расширенной памяти. . . . mov ah,40h ;проверить статус EMM int 67h or ah,ah jnz error ;перейти в случае плохого статуса mov ah,46h ;проверить версию EMM int 67h or ah,ah jnz error ;перейти, если номер версии не может быть получен cmp al,30h ;удостовериться, что версия не ниже 3.0 jb error ;перейти, если версия EMM не устраивает mov ah,41h ;получить адрес сегмента кадра страниц int 67h or ah,ah jnz error ;перейти, если ошибка в получении адреса кадра ; страниц mov page_frame,bx ;сохранить сегментный адрес кадра страниц mov ah,42h ;получить сведения о количестве доступных страниц int 67h or ah,ah jnz error ;перейти, если ошибка при получении этих сведений mov total_pages,dx ;сохранить полное количество страниц EMM mov avail_pages,bx ;сохранить доступное количество страниц EMM or bx,bx jz error ;уйти, если нет доступных страниц mov ah,43h ;попытка распределить страницы EMM mov bx,needed_pages int 67h ;если распределение успешное or ah,ah jnz error ;перейти,если распределение завершилось с ошибкой mov emm_handle,dx ;сохранить системный идентификатор для выделенных ; страниц . . ;мы готовы к иной обработке . ;используя страницы EMM . ;отобразить страницу памяти... mov bx,log_page ;BX <- номер логической страницы EMM mov al,phys_page ;AL <- физическая страница (0-3) mov dx,emm_handle ;системный идентификатор EMM для наших страниц mov ah,44h ;функция 44H = отобразить страницу EMM int 67h or ah,ah jnz error ;перейти в случае ошибки отображения . . . ;программа готова к завершению ;освободить распределенные страницы mov dx,emm_handle ;системный идентификатор для наших страниц mov ah,45h ;функция 45H = освободить страницы int 67h or ah,ah jnz error ;перейти в случае ошибки освобождения страниц . . . Рис.9-8.Схема программы, использующей расширенную память Предполагается, что факт наличия программы управления расширенной памятью уже установлен с помощью методов, показанных на рис.9-6 и 9-7. Обработчик прерываний или резидентный драйвер, использующие EMM, следуют с незначительными вариациями указанной выше процедуре, состоя- щей из восьми последовательных шагов. Они должны обладать системным идентификатором EMM и распределять страницы до полной раскрутки опера- ционной системы; в частности, функции MS-DOS Открыть файл или устройс- тво (Прерывание 21H, функция 3DH), IOCTL (Прерывание 21H, функция 44H) и Получить вектор прерываний (Прерывание 21H, функция 35H) доступными не считаются. Так, подобные обработчик и драйвер должны использовать модифицированный вариант функции "Получить вектор прерываний" для про- верки наличия EMM, непосредственно извлекая содержимое вектора преры- вания 67H вместо обращения к Прерыванию MS-DOS 21H, функции 35H. Драйвер устройства или обработчик прерываний обычно владеют стра- ницами расширенной памяти в течение всего времени функционирования системы (вплоть до ее перезагрузки) и никогда их не освобождают. Такие программы обязаны также сохранять (функция EMM 47H) и восстанавливать (функция EMM 48H) контекст механизма отображения страниц EMM (страницы EMM отображаются в кадр страниц в то же самое время, когда драйвер ус- тройства или обработчик прерываний управляют системой) таким образом, что использование расширенной памяти последующими программами будет корректным. Программа управления расширенной памятью в значительной степени по- лагается на "хорошее поведение" прикладных программ, которое не должно приводить к порче расширенной памяти. Если использующие в муль- тизадач- ном режиме расширенную память приложения, такие как Microsoft Windows и другие, не придерживаются строго протокола взаимодействия с EMM, хранимые в расширенной памяти данные могут быть запорчены. 3. Дополнительная память (Extended memory) Дополнительной считается память сверх адресного пространства в 1 Мбайт (100000H), к которой обеспечивается доступ со стороны микропро- цессоров 80286 либо 80386, работающих в защищенном режиме. IBM-PC/AT-совместимые ПЭВМ могут (теоретически) иметь до 15 Мбайт вст- роенной дополнительной памяти, в дополнение к 1 Мбайту адресного прос- транства стандартной памяти. В отличие от расширенной памяти, дополни- тельная память является линейно-адресуемой: Адрес каждой ячейки фикси- рован, поэтому отсутствует необходимость в специальных управляющих программах. Операционные системы, функционирующие в защищенном режиме, такие, как XENIX фирмы Microsoft и MS OS/2, могут использовать дополнительную память для выполнения программ. Операционная система MS-DOS, с другой стороны, исполняемая в реальном режиме на микропроцессорах 80286 или 80386, и программы, которые считаются под ее управлением, обычно не могут выполняться в дополнительной памяти, а также хранить в ней дан- ные. С целью обеспечения ограниченного доступа к дополнительной памяти со стороны MS-DOS-программ на микропроцессорах 80286 и 80386, IBM PC/AT-совместимые ПЭВМ содержат в BIOS ПЗУ две подпрограммы (табл.9-4 и 9-5). Они позволяют определить имеющееся количество дополнительной памяти (Прерывание 15H, функция 88H), а затем выполнить пересылку бло- ков данных между Стандартной и Дополнительной памятью (Прерывание 15H, функция 87H). Эти подпрограммы могут быть использованы электронными дисками (ОЗУ-дисками), а также другими программами, которые намерены обращаться к дополнительной памяти с целью быстрого запоминания и вос- становления данных взамен взаимодействия с медленным физическим дис- ком. Табл.9-4. Прерывание BIOS ПЗУ 15H, функции доступа к дополнительной памяти для IBM PC/AT. ---------------------------------------------------------------------- Функции прерывания 15H ¦ Параметры вызова ¦ Возврат ---------------------------------------------------------------------- Переслать блок дополни- ¦ AH = 87H *) ¦ Флаг состояния = 0, тельной памяти ¦ CX = длина(в словах)¦ если успешно ¦ ES:SI = адрес дис- ¦ = 1, ¦ криптора пе- ¦ если ошибочно ¦ ресылки блока ¦ AH = состояние ¦ ¦ 00H - нет ошибок ¦ ¦ 01H - ошибка четнос- ¦ ¦ ти ОЗУ ¦ ¦ 02H - ошибка прерыва- ¦ ¦ ния по исключи- ¦ ¦ тельной ситуации ¦ ¦ 03H - адресная линия ¦ ¦ 20 неисправна ---------------------------------------------------------------------- Получить объем дополни- ¦ AH = 88H ¦ AX = количество кило- тельной памяти ¦ ¦ байт памяти, ус- ¦ ¦ тановленной ¦ ¦ сверх 1 Мбайт ¦ ¦ ---------------------------------------------------------------------- *) В табл. 9-5 показан формат дескриптора, используемый функцией 87H. Табл.9-5. Формат дескриптора пересылки блока для прерывания 15H, функции 87H BIOS ПЗУ для IBM PC/AT (Пересылка блока дополни- тельной памяти). ---------------------------------------------------------------------- Байты ¦ Содержимое ---------------------------------------------------------------------- 00-0FH ¦ Нуль 10-11H ¦ Длина сегмента в байтах (2*CX - 1 или больше) 12-14H ¦ 24-битный адрес источника 15H ¦ Байт правого доступа (93H) 16-17H ¦ Нуль 18-19H ¦ Длина сегмента в байтах (2*CX - 1 или больше) 1A-1CH ¦ 24-битный адрес назначения 1DH ¦ Байт прав доступа (93H) 1E-1FH ¦ Нуль 20-2FH ¦ Нуль ---------------------------------------------------------------------- З_а_м_е_ч_а_н_и_е: Эта структура данных (в табл.9-5) образует таблицу глобальных дескрипторов (global descriptor table - GDT), кото- рый используется центральным процессором в защищенном режиме: нулевые байты на смещениях 00-0FH и 20-2FH заполняются BIOS ПЗУ перед пересыл- кой. Указываемый 24-битный адрес является линейным адресом в диапазоне 000000-FFFFFFH (то есть это не адрес сегмента и не смещение) с наиме- нее значащим байтом в качестве первого и наиболее значащим в качестве последнего. Использование этих подпрограмм в BIOS ПЗУ должно выполняться с предосторожностями. Хранимые в дополнительной памяти данные не фикси- руются там; они теряются при выключении машины. Пересылка данных из дополнительной памяти либо в нее приводит к переключению из реального режима в защищенный и обратно. Этот процесс на машинах, основанных на микропроцессорах типа 80286 является сравнительно медленным; в некото- рых случаях это незначительно быстрее, чем обращение к жесткому диску. Вдобавок, использующие дополнительную память программы (через функции BIOS ПЗУ) не совместимы со средствами работы в MS-DOS 3.x под управле- нием MS OS/2; также низка при этом надежность сетевого режима. Наконец, главный недостаток рассмотренных функций BIOS ПЗУ заклю- чается в том, что в них полностью отсутствует арбитраж между двумя или более программами либо драйверами устройств, которые используют допол- нительную память в качестве хранилища. Например, если прикладная прог- рамма и загружаемый драйвер электронного диска пытаются поместить дан- ные в одну и ту же область дополнительной памяти, ни одной из программ об ошибке не сообщается, хотя данные, принадлежащие одной либо обеим программам, могут быть разрушены. На рис.9-9 показано использование функций BIOS ПЗУ для пересылки блока данных из дополнительной памяти в стандартную память. ; дескриптор блока данных bmdt db 8 dup (0) ; пустой дескриптор db 8 dup (0) ; главный дескриптор db 8 dup (0) ; дескриптор сегмента-источника db 8 dup (0) ; дескриптор сегмента-назначения db 8 dup (0) ; дескриптор CS-сегмента для BIOS db 8 dup (0) ; дескриптор SS-сегмента для BIOS buff db 8 dup (0) ; буфер для приема данных . . . mov dx,10h ; DX:AX - дополнительная память(источник) mov ax,0 ; адрес 100 000H (1 Мбайт) mov bx,seg buff ; DS:BX - адрес стандартной памяти mov ds,bx ; в качестве назначения mov bx,offset buff mov cx,80h ; CX - длина пересылки (в байтах) mov si,seg bmdt ; ES:SI - дескриптор блока пересылки mov es,si mov si,offset bmdt call getblk ; получить блок из дополнительной памяти or ah,ah ; проверить состояние jnz error ; перейти, если пересылка завершилась . ; неудачно . . getblk proc near ; переслать блок из дополнительной памяти ; в реальную ; вызвать со следующими параметрами ; DX:AX - адрес дополнительной памяти ; DS:BX - буфер назначения ; CX - длина (в байтах) ; ES:SI - дескриптор блока пересылки ; возврат ; AH = 0, если пересылка удачная mov es:[si+10h],cx ; сохранить длину в дескрипторах mov es:[si+18h],cx ; записать байты правого доступа mov byte ptr es:[si+15h],93h mov byte ptr es:[si+1dh],93h ; адрес источника (дополнительная память) mov es:[si+12h],ax mov es:[si+14h],dl ; адрес назначения (стандартная память) mov ax,ds ; номер сегмента * 16 mov dx,16 mul dx add ax,bx ; + смещение -> линейный адрес adc dx,0 mov es:[si+1ah],ax mov es:[si+1ch],dl shr cx,1 ; преобразовать длину в слове mov ah,87h ; функция 87H = блок пересылки управления int 15h ; к BIOS ПЗУ ret ; возврат в вызывающую программу Рис.9-9. Продемонстрирована пересылка блока из дополнительной памяти в стандартную память с использованием подпрограмм BIOS ПЗУ. Процедура getblk получает адрес источника в дополнительной памяти, адрес назначения в стандартной памяти, длину в бай- тах, сегментный адрес и смещение дескриптора блока пересылки. Адрес дополнительной памяти представляет собой 32-битный ли- нейный адрес, причем значащими являются лишь младшие 24 раз- ряда. Адрес стандартной памяти - это сегментный адрес и сме- щение. Процедура getblk преобразует сегментный адрес и смеще- ние назначения в линейный адрес, строит соответствующие поля в дескрипторе блока пересылки, вызывает подпрограммы BIOS ПЗУ для выполнения пересылки, а затем возвращает статус на регис- тре AH. 4. Заключение ПЭВМ с операционной системой MS-DOS могут поддерживать три типа быстрой памяти с прямым доступом. У каждого типа есть свои специфичес- кие характеристики и для управления отдельным типом памяти требуется особая техника. Стандартная память представляет собой адресное пространство до 1 Мбайт, к которому может быть обеспечен доступ со стороны микропроцес- сора 8086 или 80886 либо со стороны микропроцессоров 80286/80386, ра- ботающих в реальном режиме. MS-DOS и программы, работающие под ее уп- равлением, выполняются в этом адресном пространстве. MS-DOS предостав- ляет прикладным программам средства для динамического выделения и ос- вобождения блоков стандартной памяти. До 8 Мбайт расширенной памяти могут использоваться на ПЭВМ для электронных дисков, копий дисков и для хранения данных прикладных программ. Память доступна страницами по 16 Мбайт и находится под уп- равлением программы управления расширенной памятью (EMM) - драйвера, который обеспечивает распределение памяти, ее освобождения, отображе- ния и мультизадачную поддержку. Дополнительная память относится к адресному пространству, превы- шающему 1 Мбайт, к которому обеспечивается доступ со стороны микропро- цессоров 80286/80386, работающих в защищенном режиме; она не доступна на ПЭВМ, оснащенной микропроцессором 8086/8088. Может быть установлено до 15 Мбайт дополнительной памяти; однако, средства BIOS ПЗУ по досту- пу к этой памяти являются весьма низкоуровневыми и медленно работающи- ми, и отсутствует программа управления, которая обеспечивала бы мно- жественный доступ программ к одному и тому же участку дополнительной памяти. Рэй Дункан Глава 10. Функция EXEC MS-DOS Системный загрузчик MS-DOS, который доставляет файлы .COM или EXEC c диска в память и выполняет их, может быть вызван любой програм- мой с функцией MS-DOS EXEC (прерывание 21H, функция 4BH). Работающий по умолчанию интерпретатор команд MS-DOS, COMMAND.COM, использует фун- кцию EXEC для загрузки и исполнения своих внешних команд, таких как CHKDSK, а также других прикладных программ. Во множестве распростра- ненных коммерческих программ, таких как системы управления базами дан- ных и текстовых процессорах, EXEC используется для загрузки и исполне- ния вспомогательных программ (например, программы орфографического контроля), или для загрузки и выполнения второй копии COMMAND.COM. Это позволяет пользователю исполнять вспомогательные программы или выхо- дить в среду команд MS-DOS без потери своего текущего рабочего контек- ста. Когда EXEC используется одной программой (главной задачей) для того, чтобы загрузить и выполнить другую программу (подзадачу), глав- ная задача может передать подзадаче определенную информацию в виде множества строк, называемых описанием среды, командной строки и двух блоков управления файлами. Подзадача также наследует от главной задачи системные идентификаторы для стандартных устройств MS-DOS и для любых других файлов или символьных устройств, открытых главной задачей, (ес- ли не выполняется команда с параметром, отменяющим наследование). Лю- бые команды, выполненные подзадачей над унаследованными системными идентификаторами, такие как поиск файлов или их ввод-вывод, воздейст- вуют также на указатели, связанные с системными идентификаторами глав- ной задачи. Подзадача может в свою очередь загружать другие программы, и этот цикл может повторяться до тех пор, пока не будет исчерпана сис- темная память. Так как MS-DOS не является многозадачной операционной системой, то подзадача полностью управляет взаимодействием с системой до тех пор, пока не прекратит свою работу; выполнение главной задачи при этом приостанавливается. Этот тип обработки иногда называется синхронным. Когда подзадача завершается, главная задача снова получает управление, и может использовать вызовы других системных функций (прерывание 21H, функция 4DH) для того, чтобы получить код возврата подзадачи и опреде- лить, завершилась ли программа нормально, или была прервана из-за кри- тической ошибки в оборудовании, или из-за того, что пользователь ввел Ctrl-C. В дополнение к загрузке подзадачи EXEC также может быть использо- вана для загрузки подпрограмм и оверлейных программ для прикладных программ, написанных на ассемблере или языке высокого уровня, которые не содержат в своей рабочей библиотеке средств управления оверлеем. Такие оверлейные программы обычно не могут быть исполнены как самосто- ятельные программы; многим требуется "помощник" в виде процедур или данных в главном сегменте прикладной программы. Функция EXEC есть только в версиях 2.0 MS-DOS и выше. С версиями MS-DOS 1.х главная задача может использовать функцию 26H прерывания 21H, чтобы создать префикс сегмента программы для подзадачи, но должна сама выполнять загрузку, размещение в памяти и выполнение подзадачи и ее данных, безо всякой помощи со стороны операционной системы. 1. Как работает EXEC Когда функция EXEC получает запрос на выполнение программы, она сначала делает попытку разместить в памяти и открыть указанный прог- раммный файл. Если файл не может быть найден, EXEC сразу же прекращает работу и возвращает в вызывающую программу код ошибки. Если этот файл существует, EXEC открывает его, определяет его размер и проверяет первый блок файла. Если первые два байта этого бло- ка являются символами MZ (в кодировке ASCII), то предполагается, что в этом файле содержится загрузочный .EXE-модуль, а размеры программы, данных и стековых сегментов берутся из заголовка файла .EXE. Иначе предполагается, что входной файл является абсолютным загрузочным обра- зом (.COM-программой). Причем указанное расширение имени файла (.COM или .EXE) при этом определении игнорируется. В этой точке становится известным количество памяти , необходи- мое, чтобы разместить программу, так что EXEC пытается выделить два блока памяти: один - для нового описания среды программы, и один для самой программы, данных и стековых сегментов. В предположении, что имеется объем памяти, достаточный чтобы содержать саму прогамму, коли- чество реально распределенной под программу памяти изменяется в зави- симости от ее типа. Программам типа .COM обычно предоставляется вся свободная оперативная память (если эта область памяти не была предва- рительно фрагментирована), тогда как количество памяти, выделяемое под программу типа .EXE, определяется по двум полям в заголовке файла, MINALLOC и MAXALLOC, значения которых устанавливаются редактором свя- зей объектных модулей Object Linker фирмы Microsoft (LINK) . (См.: "Программирование в среде MS-DOS": "Программирование для MS-DOS": "Структура прикладной программы": "Инструментальные средства програм- мирования": "Редактор связей объектных модулей Microsoft Object Linker; Программные утилиты: LINK".) EXEC затем копирует описание среды главной задачи в память, выде- ленную для описания среды подзадачи , строит префикс сегмента програм- мы (PSP) на основе блока памяти подзадачи и копирует в PSP подзадачи хвост командной строки и два подразумеваемых по умолчанию блока управ- ления файлами, переданными от главной задачи. Предыдущее содержимое векторов завершения (прерывание 22H), Ctrl-C (прерывание 23H) и крити- ческой ошибки (прерывание 24H) сохраняется в новом PSP, и вектор за- вершения корректируется таким образом, что управление будет возвращено главной задаче при окончании выполнения или аварийном завершении под- задачи. Затем собственно текст программы и порции данных подзадачи считы- ваются с дискового файла в область оперативной памяти, выделенной для этой программы, по адресам, следующим за вновь созданным PSP. Если подзадача является программой типа .EXE, то для организации ссылок на сегменты внутри программы с целью определения их абсолютного загрузоч- ного адреса используется таблица размещений из заголовка файла. Наконец, функция EXEC заполняет регистры процессора и стек в со- ответствии с типом программы и передает управление самой этой програм- ме. Точка входа для файла .COM всегда имеет смещение 100H внутри блока памяти программы (первый байт, следующий за PSP). Точка входа для фай- ла .EXE указана в заголовке файла и может находиться в любом месте внутри программы. (См. также: "Программирование в среде MS-DOS": "Программирование для MS-DOS": "Структура прикладной программы".) Когда EXEC используется для загрузки и выполнения оверлейной программы, а не подзадачи , последовательность работы гораздо проще, чем описано выше. Для оверлейной программы EXEC не пытается распреде- лять память, создавать PSP или описание среды . Она просто загружает содержимое файла по адресу, указанному в вызывающей программе, и вы- полняет любые необходимые операции по распределению памяти (если овер- лейный файл имеет заголовок типа .EXE), используя значение сегмента, которое также передается из вызывающей программы. EXEC затем возвраща- ется в вызвавшую ее программу, а не передает управление тексту только что загруженного файла. Вызывающая программа несет ответственность за вызов оверлейной программы в назначенном месте. 2. Использование EXEC для загрузки программы Когда одна программа загружает и выполняет другую, в ней должны быть выполнены следующие шаги: 1. Убедиться, что доступен объем свободной памяти, достаточный чтобы хранить текст, данные и стек подзадачи. 2. Создать информацию для передачи ее функции EXEC и подзадаче. 3. Вызвать функцию MS-DOS EXEC для запуска подзадачи. 4. Восстановить и исследовать коды завершения и возврата подзада- чи. 2.1. Выделение памяти MS-DOS обычно выделяет программам .EXE или .COM при их загрузке всю доступную память. (Редкие исключения из этого правила имеют место, когда область оперативной памяти, доступная для прикладных программ, фрагментирована присутствием резидентных данных или программ, или ког- да программа типа .EXE загружена так, что редактирование связей в ней осуществлялось с ключом /CPARMAXALLOC, или модифицировалась с помощью EXEMOD.) Следовательно, перед тем как некоторая программа сможет заг- рузить другую программу, она должна освободить всю память, которая не требуется ей для ее собственных кодов, данных и стека. Дополнительная память высвобождается с помощью функции MS-DOS "Изменить размер блока памяти" (Resize Memory Block) ( прерывание 21H, функция 4AH). В этом случае адрес сегмента PSP главной задачи переда- ется в регистре ES, а в регистре BX содержится количество участков па- мяти программы, которые она должна оставить для своего собственного использования. Если предполагаемой главной задачей является программа типа .COM, она должна убедиться, что перемещает свой стек в безопасную (сохраняемую) область памяти при сокращении своего объема памяти до размеров, меньших 64 Kбайт. 2.2. Подготовка параметров для EXEC Когда функция EXEC используется для загрузки и выполнения некото- рой программы, то она должна быть снабжена двумя принципиально важными параметрами: - адрес имени пути доступа подзадачи; - адрес блока параметров. Блок параметров в свою очередь содержит адреса информации, кото- рая должна передаваться подзадаче. 2.2.1. Имя программы Имя пути доступа для подзадачи должно быть однозначной, заканчи- вающейся нулем (в кодах ASCIIZ) спецификацией файла (без шаблонных символов). Если указание пути доступа не входит в спецификацию файла, то поиск программы осуществляется в текущем каталоге; если отсутствует имя дисковода, то используется дисковод, заданный по умолчанию. 2.2.2. Блок параметров Блок параметров содержит адреса четырех элементов данных (см. рис.10-1): - блок контекста (описания среды); - хвост команды; - два блока управления файлами (FCB), подразумеваемых по умолча- нию. Позиция, зарезервированная в блоке параметров под указатель на описание среды, равна двум байтам и содержит адрес сегмента, так как описание среды всегда выравнено по параграфу (его адрес всегда кратен 16); значение 0000H означает, что описание среды главной задачи должно быть унаследовано без изменений. Оставшиеся три адреса являются двой- ными адресными словами в стандартном формате INTEL, со значением сме- щения в младшем слове и значением сегмента в старшем. ---------------------------------------------------------------------¬ ¦ ¦ ¦ При вызове: ¦ ¦ ========== ¦ ¦ ¦ ¦ AH = 4BH ¦ ¦ AL = 00H загрузка и выполнение подзадачи ¦ ¦ 03H загрузка оверлея ¦ ¦ ¦ ¦ DS:DX = сегмент:смещение ASCIIZ-строки имени пути дос- ¦ ¦ тупа для файла выполняемой программы ¦ ¦ ES:BX = сегмент:смещение блока параметров ¦ ¦ ¦ ¦ Возврат: ¦ ¦ ======= ¦ ¦ ¦ ¦ Если функция завершена успешно: ¦ ¦ флаг состояния обнулен; ¦ ¦ содержимое других регистров сохраняется в версях MS-DOS 3.0 ¦ ¦ и выше, и уничтожается в MS-DOS версий 2.х ¦ ¦ ¦ ¦ Если функция завершилась неуспешно: ¦ ¦ устанавливается значение флага состояния; ¦ ¦ ¦ ¦ AХ = код ошибки ¦ L--------------------------------------------------------------------- ---------------------------------------------------------------------¬ ¦ Формат блока параметров ¦ ¦ ======================= ¦ ¦ _________________________________________________________________ ¦ ¦ ¦ ¦ Смещение Содержание ¦ ¦ _________________________________________________________________ ¦ ¦ Если AL = 00H (загрузка и выполнение программы) ¦ ¦ ¦ ¦ 00H указатель сегмента передаваемого описания среды ¦ ¦ 02H смещение хвоста командной строки для нового PSP ¦ ¦ 04H сегмент хвоста командной строки для нового PSP ¦ ¦ 06H смещение первого блока управления файлом, который ¦ ¦ должен быть скопирован в новое PSP со смещением 5CH ¦ ¦ 08H сегмент первого блока управления файлом ¦ ¦ 0AH смещение второго блока управления файлом, который ¦ ¦ должен быть скопирован в новое PSP со смещением 6CH ¦ ¦ 0CH сегмент второго блока управления файлом ¦ ¦ ________________________________________________________________ ¦ ¦ Если AL = 03H (загрузка оверлея) ¦ ¦ ¦ ¦ 00H адрес сегмента, куда должен быть загружен оверлей ¦ ¦ 02H коеффициент смещения загружаемого образа ¦ ¦ ________________________________________________________________ ¦ +--------------------------------------------------------------------+ ¦ ¦ ¦ Рис.10-1. Перечень соглашений при вызове функции EXEC MS-DOS (Пре-¦ ¦ рывание 21H, функция 4BH), которая может быть использова-¦ ¦ на для загрузки и выполнения подзадачи или оверлея ¦ L--------------------------------------------------------------------- 2.2.3. Описание среды Описание среды всегда начинается с границы параграфа и состоит из последовательностей заканчивающихся нулем (в коде ASCIIZ) строк вида: имя = переменная Конец входного множества строк указывается дополнительным нулевым байтом. Если указатель описания среды в блоке параметров, переданного в вызов EXEC, содержит ноль, то подзадача просто получает копию описания среды главной задачи. Главная задача может, однако, передать указатель сегмента на другое или расширенное множество строк. В противном случае (под управлением MS-DOS версии 3.0 и выше) EXEC дополняет свой блок описания среды полностью определенным маршрутом доступа подзадачи. Максимальный размер области описания среды - 32 Кбайта, так что с по- мощью этого механизма между программами может передаваться очень боль- шой объем информации. Исходное, или главное описание среды системы принадлежит команд- ному процессору, который загружается при включении или перезагрузке системы (обычно это COMMAND.COM). COMMAND.COM помещает в главное опи- сание среды системы строки, являющиеся результатами выполнения команд PATH, SHELL, PROMPT и SET, со значениями по умолчанию, всегда присутс- твующими для первых двух. Например, если MS-DOS версии 3.2 загружена с дисковода C, а команда PATH отсутствует в файле AUTOEXEC.BAT, как и команда SHELL - в файле CONFIG.SYS, то главное описание среды содержит две строки: PATH= COMSPEC=C:\COMMAND.COM Эти спецификации используются COMMAND.COM для поисков выполнимых "внешних" команд и чтобы найти собственный выполнимый файл на диске, так что он может перезагружать свою резидентную временную часть при необходимости. Когда в описании среды присутствует строка PROMPT (как результат выпоонения предыдущих команд PROMPT или SET PROMPT) COMMAND.COM использует ее для формирования вида подсказки, которую пользователь видит на экране. 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0000 43 4F 4D 53 50 45 43 3D 43 3A 5C 43 4F 4D 4D 41 COMSPEC=C:\COMMA 0010 4E 44 2E 43 4F 4D 00 50 52 4F 4D 50 54 3D 24 70 ND.COM.PROMPT=$p 0020 24 5F 24 64 20 20 20 24 74 24 68 24 68 24 68 24 $_$h $t$h$h$h$ 0030 68 24 68 24 68 20 24 71 24 71 24 67 00 50 41 54 h$h$h $q$q$q.PAT 0040 48 3D 43 3A 5C 53 59 53 54 45 4D 3B 43 3A 5C 41 H=C:\SYSTEM;C:\A 0050 53 4D 3B 43 3A 5C 57 53 3B 43 3A 5C 45 54 48 45 SM;C:\WS;C:\ETHE 0060 52 4E 45 54 3B 43 3A 5C 46 4F 52 54 48 5C 50 43 RNET;C:\FORTH\PC 0070 33 31 3B 00 00 01 00 43 3A 9C 46 4F 52 54 48 5C 31;::::C:\FORTH\ 0080 50 43 33 31 5C 46 4F 52 54 48 2E 43 4F 4D 4D PC31\FORTH.COM. Рис.10-2. Распечатка типичного описания среды под управлением MS-DOS версии 3.2. Этот частный пример содержит подразумеваемый по умолчанию параметр COMSPEC и две относительно сложные коман- дные строки PATH и PROMPT, которые были сформированы по пользовательскому файлу AUTOEXEC. Отметим два нулевых байта со смещением 73H, которые указывают на конец описания среды. За этими байтами следует имя пути доступа для программы, ко- торая владеет данным описанием среды Другие строки в описании среды используются только для информаци- онных целей временными программами и не воздействуют на работу собст- венно операционной системы. Например, Microsoft C Compiler и Microsoft Object Linker ищут в описании среды строки INCLUDE, LIB и TMP, которые указывают размещение файлов include, библиотечных и временных рабочих файлов. На рис.10-2 содержится шестнадцатеричный дамп типичного блока описания среды. 2.2.4. Хвост команды Хвост команды, который передается подзадаче, имеет форму байта, указывающего длину остатка хвоста команды, за которым следует строка символов ASCII, заканчивающаяся символом ASCII возврата каретки (0DH); символ возврата каретки не включается в байт длины. Хвост команды мо- жет содержать ключи, имена файлов и другие параметры, которые могут просматриваться подзадачей и обычно влияют на ее работу. Он копируется в PSP подзадачи со смещением 80H. Когда COMMAND.COM использует EXEC для запуска программы, то пере- дается хвост команды, который содержит все, что пользователь указал в командной строке, за исключением имени программы и всех параметров пе- реадресации. Переадресация ввода/вывода обрабатывается внутри самой COMMAND.COM и проявляется в поведении системных идентификаторов стан- дартных устройств, которые унаследованы подзадачей . Любая другая программа, которая использует EXEC для запуска подзадачи , должна по- пытаться выполнить любую необходимую переадресацию на себя передать соответствующий хвост команды с тем, чтобы подзадача вела себя так, как если бы она была загружена с помощью COMMAND.COM. 2.2.5. Неявные блоки управления файлом Два неявных блока управления файлом (FCB), на которые указывает блок параметров EXEC, копируются в PSP подзадачи со смещениями 5CH и 6CH. (См.также: "Программирование в среде MS-DOS": "Программирование для MS-DOS": "Управление файлами и записями".) Блоки управления файлами (FCB) для файлов и записей ввода-вывода редко используются сейчас в распространенных прикладных программах, так как они не поддерживают иерархической структуры каталога. Но в не- которых программах все же осуществляется просмотр неявных FCB как быс- трый способ отделить первые два ключа или другие параметры от хвоста команды. Таким образом, чтобы сделать свои собственные намерения ясны- ми для подзадачи, главная задача должна моделировать действия COMMAND.COM, то есть произвести синтаксический анализ первых двух па- раметров хвоста команды и записать его результаты в неявных FCB. Для этого удобно применять функцию MS-DOS "Произвести синтаксический ана- лиз имени файла" (прерывание 21H, функция 29H). Если для подзадачи не требуется один или оба неявных FCB, то со- ответствующий адрес в блоке параметров может быть инициализирован, чтобы указывать на два пустых FCB в области памяти прикладной програм- мы. Эти пустые FCB должны состоять из одного нулевого байта, за кото- рым следует 11 байтов, содержащих символы "пробел" в коде ASCII (20H). 2.3. Выполнение подзадачи После того, как главная задача создаст необходимые параметры, она может вызвать функцию EXEC обращением к прерыванию 21H с регистрами, установленными следующим образом: AH = 4BH AL = 00H (подфункция EXEC для загрузки и выполнения программ) DS:DX = сегмент:смещение пути доступа программы ES:BX = сегмент:смещение блока параметров По возвращении из программного прерывания главная задаача должна проверить флаг состояния, чтобы определить, выполнялась ли фактически подзадача . Если флаг состояния не установлен , то это значит, что подзадача была успешно загружена и получила управление. Если флаг сос- тояния установлен , то функция EXEC аварийно завершилась, и в регистре AX возвращен код ошибки для выяснения причин. Обычно причины следую- щие: - не мог был найден специфицированный файл; - файл был найден, но было недостаточно свободной оперативной па- мяти для его загрузки. Другие причины достаточно редки и могут слуджить признаком нали- чия более серьезных проблем в системе в целом (таких как повреждение файла - на диске или в памяти MS-DOS). При работе с версиями MS-DOS 3.0 и выше дополнительные детали о причинах аварийного завершения EXEC могут быть получены путем последующего вызова функции 59H прерывания 21H ("Получить расширенную информацию об ошибке"). В общем случае, получение либо неправильного адреса блока пара- метров EXEC, либо неправильных адресов внутри самого блока не является причиной аварийного завершения функции EXEC, но может приводить к неп- редвиденному поведению подзадачи. 2.3.1. Особенности для различных версий При работе с версиями MS-DOS 2.x предыдущее содержимое регистров главной задачи, включая указатель стека в SS:SP, за исключением CS:IP, может быть уничтожено после вызова EXEC. Следовательно, перед вызовом EXEC главная задача должна упрятать в стек содержимое всех тех регист- ров, которые требуется сохранить, а затем она должна сохранить этот стековый сегмент и смещение в месте памяти, которое адресуется регист- ром сегмента CS. После возврата стековый сегмент и смещение могут быть загружены в SS:SP с перекрытием сегмента программы, а затем извлечени- ем из стека могут быть восстановлены и другие регистры. В версиях MS-DOS 3.0 и выше регистры сохраняются обычным образом на протяжении всего вызова EXEC. З_а_м_е_ч_а_н_и_е. Кодовые сегменты прикладных программ Windows, в которых используется эта методика, должны быть снабжены атрибутом IMPURE. Кроме того, ошибка в MS-DOS версии 2.0 и PC-DOS версии 2.0 и 2.1 приводит к разрушению произвольного двойного слова в стековом сегменте главной задачи во время вызова EXEC. Когда главной задачей является программа типа .COM и SS=PSP, то поврежденный участок попадает внутрь PSP и не приносит вреда; однако в случае, когда главной задачей явля- ется программа типа .EXE при DS=SS, поврежденный участок может перек- рывать сегмент данных и вызвать непредсказуемое поведение или даже аварийное завершение после возвращения из EXEC. Эта ошибка была исп- равлена в MS-DOS версии 2.11 и выше, и PC-DOS версии 3.0 и выше. 2.4. Проверка кодов возврата подзадачи Если функция EXEC завершена успешно, то главная задача может выз- вать функцию 4DH прерывания 21H ("Получить кода возврата подзадачи"), чтобы определить, завершилась ли подзадача нормально с передачей кода возврата, или же она была завершен операционной системой из-за како- го-либо внешнего события. Функция 4DH возвращает: AH = тип завершения: 00H - Подзадача завершилась нормально (то есть осуществила вы- ход через прерывание 20H или функции 00H или 4CH прерывания 21H) 01H - Подзадача была завершена по вводу пользователем Ctrl-C 02H - Подзадача была завершена обработчиком критических ошибок (либо пользователь ответил A на запрос "ABORT, RETRY, IGNORE" от используемого по умолчанию системного обработчи- ка прерывания 24H или от обычного системного обработчика прерывания 24H, возвратившего управление MS-DOS с рабочим кодом = 02H в регистре AL). 03H - Поодзадача завершилась нормально и осталась резидентной (то есть осуществила выход через функцию 31H прерывания 21H, или прерывание 27H). AL = код возврата: - значение, переданное подзадачей в регистре AL, когда она завер- шилась с функцей 4CH или 31H прерывания 21H. - 00H, если подзадача завершилась с использованием прерывания 20H, прерывания 27H или функции 00H прерывания 21H. Эти значения являются единственными, возврат которых гарантирует- ся функцией 4DH. Таким образом, последующий вызов функции 4DH, без вмешательства вызова EXEC, не обязательно возвращает какую-либо полез- ную информацию. Кроме того, если функция 4DH вызывается без предшест- вующего успешного вызова EXEC , возвращаемое значение бессмысленно. 2.5. Использование COMMAND.COM вместе с EXEC Прикладная программа может "выходить в среду" MS-DOS - то есть снабжать пользователя подсказкой от MS-DOS без завершения самой прик- ладной программы - используя EXEC для загрузки и выполнения второй ко- пии COMMAND.COM с пустым хвостом командной строки. Прикладная програм- ма может получать местоположение файла COMMAND.COM на диске путем про- верки ее собственного описания среды на наличие строки COMPSEC. Поль- зователь возвращается в прикладную программу из вторичного командного процессора, введя exit по подсказке COMMAND.COM. Интерпретация командного файла выполняется COMMAND.COM, и команд- ный файл (с расширением имени .BAT) не может быть вызван с использова- нием непосредственно функции EXEC. Аналогично, последовательный поиск файлов типа .COM, .EXE и .BAT везде, где это указано в переменной PATH описания среды, - это функция COMMAND.COM, а не EXEC. Чтобы выполнить командный файл или осуществить поиск системного маршрута доступа для программы, прикладная программа может использовать EXEC для того, что- бы загрузить и выполнить вторичную копию COMMAND.COM, используемую как промежуточную. Прикладная программа находит адрес COMMAND.COM, как описано в предыдущем параграфе, но он передает хвост командной строки в следующей форме: /C program parameter1 parameter2... где program - это файл типа .EXE, .COM или .BAT, который должен быть выполнен. Когда программа program завершается, то осуществляется выход из вторичной копии COMMAND.COM и управление передается главной задаче. 2.6. Пример главной задачи и подзадачи Исходные программы PARENT.ASM на рис.10-3 и CHILD.ASM на рис.10-4 иллюстрируют, как одна программа использует EXEC, чтобы вызвать дру- гую. name PARENT title 'PARENT process - демонстрация вызова EXEC' ; ; PARENT.EXE - демонстрация ЕХЕС для запуска процесса ; ; Использует функцию MS-DOS EXEC (Прерывание 21H, функция 4ВH, подфун- ; кция 00H) для загрузки и выполнения подзадачи, названной ; CHILD.EXE, затем выводит на экран код возврата подзадачи ; ; Рэй Дункан, июнь 1987 ; stdin equ 0 ; стандартный ввод stdout equ 1 ; стандартный вывод stderr equ 2 ; стандартный код ошибки stksize equ 128 ; длина стека cr equ 0dh ; код ASCII возврата каретки if equ 0ah ; код ASCII перевода строки DGROUP group _DATA, _ENVIR, _STACK _TEXT segment byte public 'CODE' ;выполняемый сегмент программы assume cs:_TEXT, ds:_DATA, ss:_STACK stk_seg dw ? ;исходное содержимое сегмента стека SS stk_ptr dw ? ;исходное содержимое указателя стека SP main proc far ;точка входа из MS-DOS mov ax,_DATA ;назначить DS наш сегмент данных mov ds,ax ;возвратить дополнительную память, так как ;подзадача должна иметь место для выполне- ;ния mov ax,es ;пусть AX = базовый сегмент PSP mov bx,ax ;и BX = базовый сегмент стека sub bx,ax ;резервировать сегмент стека и сегмент PSP add bx,stksize/16 ;добавить параграфы стека mov ah,4ah ;функция 4AH - модифицировать блок памяти int 21h jc main1 ;вывести на экран сообщение главной задачи mov dx,offset DGROUP:msg1 ; DS:DX -адрес сообщения mov cx,msg1_len ; CX - длина сообщения call pmsg push ds mov stk_seg,ss ;сохранить сегмент данных главной задачи mov stk_ptr,sp ;сохранить указатель стека главной задачи ;сейчас выполняется подзадача mov ax,ds ;назначить ES=DS mov es,ax mov dx,offset DGROUP:cname ; DS:DX= имя пути доступа подза- ; дачи mov bx,offset DGROUP:pars ; ES:BX= блок параметров mov ax,4b00h ; функция 4BH, подфункция 00H int 21h ; передача управления MS-DOS cli ;/в случае ошибки в ранних процессорах 8088/ mov ss,stk_seg ;восстановить указатель стека главной задачи mov sp,stk_ptr sti ;/в случае ошибки в ранних процессорах 8088/ pop ds ; восстановить DS - наш сегмент данных jc main2 ; переход в случае сбоя EXEC ; иначе если EXEC успешно выполнен ; преобразовать и вывести на экран ; коды завершения и возврата подзадачи mov ah,4dh ; функция 4DH = получить код возврата int 21h ; передача управления MS-DOS xchg al,ah ; преобразовать код завершения mov bx,offset DGROUP:msg4a call b2hex mov al,ah ; возвратить код возврата mov bx,offset DGROUP:msg4b ; и преобразовать его call b2hex mov dx,offset DGROUP:msg ; DS:DX = адрес сообщения mov cx,msg4_len ; СХ = длина сообщения call pmsg ; вывод его на экран mov ax,4c00h ; нет ошибок, завершение программы int 21h ; с кодом возврата = 0 main1: mov bx,offset DGROUP:msg2a ; преобразовать код ошибки call b2hex mov dx,offset DGROUP:msg2 ; вывести на экран сообщение mov cx,msg2_len ;"Сбой распределения памяти..." call pmsg jmp main3 main2: mov bx,offset DGROUP:msg3a ; преобразовать код ошибки call b2hex mov dx,offset DGROUP:msg3 ; вывести на экран сообщение mov cx,msg3_len ; "Сбой вызова ЕХЕС " call pmsg main3: mov ax,4c01h ; ошибка, завершение программы int 21h ; с кодом возврата = 1 main endp ; конец главной процедуры b2hex proc near ;преобразовать байт в шестнадцатеричный ASCII ;вызов с AL = двоичное значение ; BX = адрес для сохранения строки push ax shr al,1 shr al,1 shr al,1 shr al,1 call ascii ; пришел первый символ ASCII mov [bx],al ; сохранить его pop ax and al,0fh ; отделить следующие четыре бита (следующие call ascii ; разряды), образующие второй символ ASCII; mov [bx+1],al ; сохранить его ret b2hex endp ascii proc near ;преобразовать значение 00-0FH в AL в шест- add al,'0' ;надцатеричный ASCII-символ cmp al,'1' jle ascii2 ; переход, если в диапазоне 00-09H add al,'A'-'9'-1 ; смещение его в диапазон 0A-0FH ascii2 ret ; возврат ASCII-символа в AL ascii endp pmsg proc near ;вывести на стандартное устройство вывода сообщение ;вызов с DS:DX = адрес, ; СХ = длина mov bx,stdout ; BX = стандартный системный идентификатор ; устройства вывода mov ah,40h ; функция 40H = запись в файл/вывод на устрой- ; ство int 21h ; передача управления MS-DOS ret ; возврат в вызывающую программу pmsg endp _TEXT ends _DATA segment para public 'DATA' ; сегмент статических и переменных ; данных cname db 'CHILD.EXE',0 ; имя пути для подзадачи pars dw _ENVIR ; сегмент блока dd tail ; длинный адрес, хвост команды dd fcb1 ; длинный адрес, по умолчанию FCB #1 dd fcb2 ; длинный адрес, по умолчанию FCB #2 tail db fcb1-tail-2 ; хвост команды для подзадачи db 'dummy command tail',cr fcb1 db 0 ; скопировано в FCB #1 по умолчанию в db 11 dup (' ') ; префиксе сегмента подзадачи db 25 dup(0) fcb2 db 0 ; скопировано в FCB #2 по умолчанию в db 11 dup (' ') ; префиксе сегмента подзадачи db 25 dup(0) msg1 db cr, lf 'Parent executing!', cr, lf msg2_len equ $-msg1 msg2 db cr,lf,'Memory resize failed, error code=' msg2a db 'xxh.',cr,lf msg2_len equ $-msg2 msg3 db cr,lf,'EXEC call failed, error code=' msg3a db 'xxh.',cr,lf msg3_len equ $-msg3 msg4 db cr,lf,'Parent reqainer control' db cr,lf,'Child terminateion type=' msg4a db 'xxh.',cr,lf msg4b db 'xxh, return code' msg4_len equ $-msg4 _DATA ends _ENVIR segment para public 'DATA' ; пример передачи подзадаче ; блока описания среды db 'PATH=',0 ; основные строки PATH, PROMPT db 'PCOMPT=$p$_$n$g',0 ; и COMSPEC db 'COMSPEC=C:\COMMAND.COM',0 db 0 ;дополнительный ноль завершает блок _ENVIR ends _STACK segment para stack 'STACK' db stksize dup (?) _STACK ends end main ;определение точки входа в программу Рис.10-3.PARENT.ASM , исходный текст программы PARENT.EXE _____________________________________________________________________ name child title 'CHILD process' ; ;CHILD.EXE - простой процесс, загружаемый PARENT.EXE для ;демонстрации вызова процедуры MS-DOS EXEC (Подфункция 00H) ; ; Рэй Дункан, июнь 1987 ; stdin equ 0 ; стандартный ввод stdout equ 1 ; стандартный вывод stderr equ 2 ; стандартный код ошибки cr equ 0dh ; код ASCII возврата каретки if equ 0ah ; код ASCII перевода строки DGROUP group _DATA, _STACK _TEXT segment byte public 'CODE' ;выполняемый сегмент программы assume cs:_TEXT, ds:_DATA, ss:_STACK main proc far ; точка входа из MS-DOS mov ax,_DATA ; назначить DS наш сегмент данных mov ds,ax ; вывести на экран сообщение подзадачи mov dx,offset DGROUP:cname ; DS:DX= адрес сообщения mov cx,msg_len ; СХ = длина сообщения mov bx,stdout ; BX = стандартное устройство вывода mov ah,40h ; AH = функция 40H - вывод в файл/на ; устройство int 21h ; передача управления MS-DOS jc main2 ; переход по любой ошибке mov ax,4c00h ; ошибок нет, завершение подзадачи int 21h ; с кодом возврата 0 main2: mov ax,4c01h ; ошибка, завершение подзадачи int 21h ; с кодом возврата 1 main endp ; конец главной процедуры _TEXT ends _DATA segment para public 'DATA' ; сегмент статических и переменных ; данных msg db cr,lf,'Child executing!',cr,lf msg_len equ $-msg _DATA ends _STACK segment para stack 'STACK' dw 64 dup (?) _STACK ends end main ; определение точки входа в программу Рис.10-4. CHILD.ASM, исходный текст программы CHILD.EXE _____________________________________________________________________ Программа PARENT.ASM может быть ассемблирована и скомпонована в выполнимую программу PARENT.EXE с помощью следующих команд: C>MASM PARENT; C>LINK PARENT; Аналогично, программа CHILD.ASM может быть ассемблирована и ском- понована в файл CHILD.EXE следующим образом: C>MASM CHILD; C>LINK CHILD; Когда PARENT.EXE выполняется по команде: C> PARENT программа PARENT сокращает занимаемый ею объем оперативной памяти при помощи вызова функции 4AH прерывания 21H, чтобы максимизировать количество свободной памяти в системе, и затем вызывает функцию EXEC, чтобы загрузить и выполнить CHILD.EXE. CHILD.EXE выполняется точно также, как если бы она была загружена непосредственно COMMAND.COM. CHILD переустанавливает регистр сегмента DS так, чтобы он указывал на ее собственный сегмент данных, использует функцию 40H прерывания 21H, чтобы передать сообщение на стандартное устройство вывода, а затем завершается, используя функцию 4CH прерыва- ния 21H, передавая код возврата ноль. Когда PARENT.EXE вновь получает управление, она сначала проверяет флаг состояния, чтобы определить, успешным ли был вызов EXEC. Если вы- зов EXEC закончился неудачей, PARENT выводит на экран сообщение об ошибке и завершается с функцией 4CH прерывания 21H, передавая ненуле- вой код возврата в COMMAND.COM, чтобы указать на ошибку. В противном случае PARENT использует функцию 4DH прерывания 21H, чтобы получить тип завершения и код возврата от CHILD.EXE, которые преобразует в ASCII-символы и выводит на экран. Затем PARENT заверша- ется, используя функцию 4CH прерывания 21H, и передает код возврата, равный нулю, в COMMAND.COM, указывая на успешное выполнение. COMMAND.COM, в свою очередь, получает управление и выводит на экран новую подсказку пользователю. 3. Использование EXEC для загрузки оверлейных программ Загрузка оверлейных программ с помощью функции EXEC намного про- ще, чем использование EXEC для выполнения других программ. Главная программа, называемая корневым сегментом, должна выполнить следующие шаги для того, чтобы загрузить и исполнить оверлейную программу: 1. Сделать блок памяти пригодным для получения оверлейной прог- раммы. 2. Подготовить блок параметров оверлейной программы для передачи функции EXEC. 3. Вызвать функцию EXEC для загрузки оверлейной программы. 4. Выполнить код внутри оверлейной программы путем перехода на него с помощью межсегментного вызова. Сама оверлейная программа может быть создана либо как образ памя- ти (.COM), либо как перемещаемый файл (.EXE), и не обязательно должна быть того же типа, что и корневая программа. В любом случае, оверлей- ная программа должна быть спроектирована так, чтобы точка входа (или указатель на точку входа) находился в начале модуля после его загруз- ки. Это позволяет корневому и оверлейному модулям выполняться отдель- но, и избавляет корневую программу от необходимости иметь "магические" знания об адресах внутри оверлейной программы. Чтобы предостеречь пользователя от неумышленного исполнения овер- лейной программы непосредственно из командной строки, оверлейным фай- лам должны быть присвоены расширения, отличные от .COM или .EXE. Наи- более удобным способом указания связи оверлейных программ и их корне- вого сегмента является присваивание им одинакового файлового имени, но с расширениями, такими как .OVL или OVL1, OVL2 и так далее. 3.1. Выделение оперативной памяти Чтобы EXEC осуществила загрузку подзадачи успешно, главная задача должна освободить память. В отличие от этого EXEC загружает оверлейную программу в память, которая п_р_и_н_а_д_л_е_ж_и_т вызывающей програм- ме. Если корневой сегмент является программой типа .COM и не освободил явно оперативную память, то программа корневого сегмента нуждается только в гарантии, что в системе есть достаточный объем оперативной памяти, чтобы загрузить оверлейную программу, и что адрес загрузки оверлейной программы не будет пересекаться с областями, содержащими ее собственный текст, данные и стеки. Если корневой сегмент программы загружается из файла типа .EXE, то для него не существует прямого пути, чтобы ясно определить, какой объем памяти он занял. Простейший способ для программы - это освобо- дить всю дополнительную память так, как это указано выше в пункте о загрузке подзадачи , а затем использовать функцию распределения памяти MS-DOS (прерывание 21H, функция 48H), чтобы получить новый блок памя- ти, достаточно большой, чтобы содержать оверлейную программу. 3.2. Подготовка параметров оверлейной программы При использовании функции EXEC для загрузки оверлейной программы, ей необходимы два основных параметра: - адрес пути доступа для файла оверлейной программы; - адрес блока параметров оверлейной программы. Как и для подзадачи , имя пути доступа для файла оверлейной прог- раммы должно быть однозначной спецификацией файла в символах ASCIIZ (опять-таки без шаблонных символов), и оно должно влючать явно опреде- ленное расширение. Как указано раньше, если маршрут и/или дисковод не включены в имя пути додступа, то используются текущий каталог и подра- зумеваемый по умолчанию дисковод. Блок параметров оверлейной программы содержит адрес сегмента, по которому оверлейная программа должна быть загружена, и фиксированное значение, которое применяется как смещение к любому перемещаемому объ- екту внутри файла оверлейной программы. Если файл оверлейной программы организован в формате .EXE, то это фиксированное значение обычно то же, что и адрес загрузки; если оверлейная программа имеет формат обра- за памяти (.COM), то это фиксированное значение должно быть нулевым. Функция EXEC не пытается проверять значения адреса загрузки и фиксиро- ванного значения или устанавливать тот факт, что адрес загрузки дейст- вительно принадлежит вызывающей программе. 3.3. Загрузка и выполнение оверлейной программы После того, как программа корневого сегмента подготовила заголо- вок файла и блока параметров оверлейной программы, она может вызвать функцию EXEC, чтобы загрузить оверлейную программу путем запроса пре- рывания 21H с регистрами, установленными следующим образом: AH = 4BH AL = 03H (подфункция EXEC для загрузки оверлейной программы) DS:DX = сегмент:смещение имени пути доступа оверлейной программы ES:BX = сегмент:смещение блока параметров оверлейной программы После возврата из прерывания 21H корневой сегмент должен прове- рить текущий флаг состояния для определения, загрузилась ли оверлей- ная программа. Если флаг состояния не установлен, файл оверлейной программы был размещен и перенесен в память по требуемому адресу. В оверлейную программу затем можно войти при помощи межсегментного вызо- ва (far call) и следует выйти обратно в корневой сегмент при помощи межсегментного возврата. П_р_и_м_е_ч_а_н_и_е р_е_д_а_к_т_о_р_а. В ма- териалах по Microsoft C используется термин 'far pointer', который пе- реводят как "дальний указатель". Здесь мы сочли более точным переводом для 'far call' тер- мин "межсегментный вызов" для указания отличия об- ращения внутри одного сегмента памяти (с передачей только адреса сме- щения относительно нача- ла сегмента) от обращения между сегментами (с передачей как адреса смещения, так и базового адреса текущего сегмента команд - 64-Кбайто- вого участка памяти). Аналогичные отличия сущес- твуют и для внутрисег- ментного возврата, при котором передается только адрес смещения, и межсегментного возврата (far return), при ко- тором передаются одновре- менно адрес смещения и базовый адрес сег- мента. Если флаг состояния установлен, то оверлейный файл не был найден, или возникла другая (возможная серьезная) системная ошибка, и регистр AX содержит код ошибки. В версии MS-DOS 3.0 и выше можно использовать функцию 59H прерывания 21H для того, чтобы получить более подробную информацию об аварийном завершении EXEC. Неверный адрес загрузки, пе- реданный в блоке параметров оверлейной программы, как правило не вызы- вает сбоя в работе самой функции EXEC, но в конце работы корневой программы может привести к выдаче обескураживающего сообщения: "Ошибка в распределении памяти, система остановлена". 3.4. Пример оверлейной программы Исходные тексты программ ROOT.ASM на рис.10-5 и OVERLAY.ASM на рис.10-6 демонстрируют использование EXEC для загрузки оверлейной программы. Программа ROOT.EXE выполняется по команде, вводимой после подсказки MS-DOS; она представляет собой корневой сегмент прикладной программы. Оверлейная программа сконструирована как файл типа .EXE (хотя она называется OVERLAY.OVL, так как не может выполняться авто- номно) и представляет собой подпрограмму, которая может быть загружена корневым сегментом, когда это ему будет необходимо. name ROOT title 'ROOT--- demonstrate EXEC overlay ; ; ROOT.EXE - демонстрация использования ЕХЕС для оверлейной программы ; ; Использует функцию MS-DOS EXEC (Прерывание 21H, функция 4ВH, подфун- ; кция 03H) для загрузки оверлейной программы, названной OVERLAY.OVL, ; вызывает подпрограмму внутри OVERLAY, затем возвращает управление и ; завершается ; ; Рэй Дункан, июнь 1987 ; stdin equ 0 ; стандартный ввод stdout equ 1 ; стандартный вывод stderr equ 2 ; стандартный код ошибки stksize equ 128 ; длина стека cr equ 0dh ; код ASCII возврата каретки if equ 0ah ; код ASCII перевода строки DGROUP group _DATA, _STACK _TEXT segment byte public 'CODE' ; выполняемый сегмент программы assume cs:_TEXT, ds:_DATA, ss:_STACK stk_seg dw ? ; исходное содержимое сегмента стека SS stk_ptr dw ? ; исходное содержимое указателя стека SP main proc far ; точка входа из MS-DOS mov ax,_DATA ; назначить DS наш сегмент данных mov ds,ax ; возвратить дополнительную память mov ax,es ; пусть AX = базовый сегмент PSP mov bx,ss ; и BX = базовый сегмент стека sub bx,ax ; резервировать сегмент стека и сегмент PSP add bx,stksize/16 ; добавить параграфы стека mov ah,4ah ; функция 4AH - модифицировать блок памяти int 21h jc main1 ; выдать на экран сообщение "Корневой сегмент ; выполняется..." mov dx,offset DGROUP:msg1 ; DS:DX = адрес сообщения mov cx,msg1_len ; CX = длина сообщения call pmsg ; резервировать память для оверлея mov bx,1000h ; получить 64 Кб (4096 параграфов) mov ah,48h ; функция 48H,разместить блок памяти int 21h ; передача управления MS-DOS jc main2 ; переход, если сбой при размещении mov pars,ax ; назначить адрес загрузки оверлея mov pars+2,ax ; назначить сегмент повторного раз- ; мещения для оверлея mov word ptr entry+2,ax ; назначить сегмент точки входа push ds mov stk_seg,ss ; сохранить сегмент данных корня mov stk_ptr,sp ; сохранить указатель стека корня ;теперь использовать ЕХЕС для загрузки оверлея mov ax,ds ; установить ES = DS mov es,ax mov dx,offset DGROUP:oname ; DS:DX= имя пути доступа оверлея mov bx,offset DGROUP:pars ; ES:BX= блок параметров mov ax,4b03h ; функция 4BH, подфункция 03H int 21h ; передача управления MS-DOS cli ;/в случае ошибки в ранних процессорах 8088/ mov ss,stk_seg ;восстановить указатель стека корня mov sp,stk_ptr sti ;/в случае ошибки в ранних процессорах 8088/ pop ds ; восстановить DS - наш сегмент данных jc main3 ; переход в случае сбоя EXEC ; иначе EXEC успешно выполнен... push ds ; сохранить наш сегмент данных call dword ptr entry ;сейчас вызов оверлея pop ds ;восстановить наш сегмент данных ;вывести на экран сообщение, что кор- ;невой сегмент получил назад управление mov dx,offset DGROUP:msg5 ; DS:DX= адрес сообщения mov cx,msg5_len ; CX = длина сообщения call pmsg ; вывод на экран mov ax,4c00h ; нет ошибок, завершение программы int 21h ; с кодом возврата = 0 main1: mov bx,offset DGROUP:msg2a ; преобразовать код ошибки call b2hex mov dx,offset DGROUP:msg2 ; вывести на экран сообщение mov cx,msg2_len ;"Сбой перераспределения памяти" call pmsg jmp main4 main2: mov bx,offset DGROUP:msg3a ; преобразовать код ошибки call b2hex mov dx,offset DGROUP:msg3 ; вывести на экран сообщение mov cx,msg3_len ;"Сбой распределения памяти..." call pmsg main3: mov bx,offset DGROUP:msg4a ; преобразовать код ошибки call b2hex mov dx,offset DGROUP:msg4 ; вывести на экран сообщение mov cx,msg4_len ; "Сбой вызова ЕХЕС..." call pmsg main4: mov ax,4c01h ; ошибка, завершение программы int 21h ; с кодом возврата = 1 main endp ; конец главной процедуры b2hex proc near ;преобразовать байт в шестнадцатеричный ASCII ;вызов с AL = двоичное значение ; BX = адрес для сохранения строки push ax shr al,1 shr al,1 shr al,1 shr al,1 call ascii ; пришел первый символ ASCII mov [bx],al ; сохранить его pop ax and al,0fh ; отделить следующие четыре бита (следующие call ascii ; разряды), образующие второй символ ASCII; mov [bx+1],al ; сохранить его ret b2hex endp ascii proc near ;преобразовать значение 00-0FH в AL в шест- add al,'0' ;надцатеричный ASCII-символ cmp al,'1' jle ascii2 ; переход, если в диапазоне 00-09Н add al,'A'-'9'-1 ; смещение его в диапазон 0А-0FH ascii2 ret ; возврат ASCII-символа в AL ascii endp pmsg proc near ;вывести на стандартное устройство вывода сообщение ; вызов с DS:DX = адрес, ; СХ = длина mov bx,stdout ;BX = стандартный системный идентификатор ; устройства вывода mov ah,40h ;функция 40Н = запись в файл/вывод на устрой- ;ство int 21h ;передача управления MS-DOS ret ;возврат в вызывающую программу pmsg endp _TEXT ends _DATA segment para public 'DATA' ; сегмент статических и переменных ; данных oname db 'CHILD.EXE',0 ; имя пути доступа для оверлея pars dw 0 ; загрузить адрес (сегмент) для файла dw 0 ; перераспределить (сегмент) для файла entry dd 0 ; точка входа для оверлея msg1 db cr, lf "Root segment executing!', cr, lf msg2_len equ $-msg1 msg2 db cr,lf,'Memory resize failed, error code=' msg2a db 'xxh.',cr,lf msg2_len equ $-msg2 msg3 db cr,lf,'Memory allocation failed, error code=' msg3a db 'xxh.',cr,lf msg3_len equ $-msg3 msg4 db cr,lf,'EXEC call failed, error code=' msg4a db 'xxh.',cr,lf msg4_len equ $-msg4 msg5 db cr,lf,'Root segment reqainer control',cr,lf msg5_len equ $-msg4 _DATA ends _STACK segment para stack 'STACK' db stksize dup (?) _STACK ends end main ; определение точки входа в программу Рис.10-5. ROOT.ASM , исходный текст программы ROOT.EXE _____________________________________________________________________ name overlay title 'OVERLAY process' ; ; OVERLAY.OVL - простой оверлейный сегмент, загружаемый ROOT.EXE для ; демонстрации использования вызова процедуры MS-DOS EXEC (Подфункция ; 03H). ; ; Этот оверлейный сегмент не содержит сегмента стека, так как он ис- ; пользует сегмент стека программы ROOT. ; ; Рэй Дункан, июнь 1987 ; stdin equ 0 ; стандартный ввод stdout equ 1 ; стандартный вывод stderr equ 2 ; стандартный код ошибки cr equ 0dh ; код ASCII возврата каретки if equ 0ah ; код ASCII перевода строки _TEXT segment byte public 'CODE' ; выполняемый сегмент программы assume cs:_TEXT, ds:_DATA ovlay proc far ; точка входа из сегмента ROOT mov ax,_DATA ; назначить DS локальный сегмент данных mov ds,ax ;вывести на экран сообщение оверлея mov dx,offset msg ; DS:DX = адрес сообщения mov cx,msg_len ; СХ = длина сообщения mov bx,stdout ; BX = стандартный системный идентификатор ; устройства вывода mov ah,40h ; AH = функция 40H - вывод в файл/на уст- ; ройство int 21h ; передача управления MS-DOS ret ; возврат в корневой сегмент ovlay endp ; конец оверлейной процедуры _TEXT ends _DATA segment para public 'DATA' ; сегмент статических и переменных ; данных msg db cr,lf 'verlay executing!', cr,lf msg_len equ $-msg _DATA ends end Рис.10-6. OVERLAY.ASM, исходный текст программы OVERLAY.OVL Программа ROOT.ASM может быть ассемблирована и скомпонована в вы- полнимую программу ROOT.EXE с помощью следующих команд: C>MASM ROOT; C>LINK ROOT; Аналогично, программа OVERLAY.ASM может быть ассемблирована и скомпонована в файл OVERLAY.OVL следующим образом: C>MASM OVERLAY; C>LINK OVERLAY,OVERLAY.OVL; Редактор связей объектных модулей Microsoft Object Linker выдаст на экран сообщение: Warning: no stack segment ("Предупреждение: нет стекового сегмента"), но это сообщение можно игнорировать. Когда программа ROOT.EXE выполняется по команде: C> ROOT она сначала сжимает выделенный ей объем оперативной памяти путем обращения к функции 4AH прерывания 21H, а затем выделяет отдельный блок опперативной памяти для оверлейного сегмента при помощи функции 48H прерывания 21H. Затем программа ROOT вызывает функцию EXEC для загрузки файла OVERLAY.OVL в только что выделенный блок оперативной памяти. Если выполнение функции EXEC завершается неудачно, программа ROOT выводит на экран сообщение об ошибке и завершает свое выполнение при помощи функции 4CH прерывания 21H, передавая ненулевой код возвра- та в COMMAND.COM для указания ошибки. Если выполнение функции EXEC за- вершается успешно, программа ROOT сохраняет содержимое своего регистра сегмента DS, а затем передает управление оверлейному сегменту при по- мощи непрямого межсегментного вызова. Оверлейная программа переустанавливает содержимое регистра сег- мента DS так, чтобы он указывал на ее собственный сегмент данных, при помощи функции 40H прерывания 21H выводит на экран некоторое сообщение и затем возвращает управление вызвавшей ее программе. Обратите внима- ние на то, что основная процедура оверлея в данном примере объявлена при помощи межсегментных атрибутов для того, чтобы заставить ассемблер сгенерировать оптимизированный код для межсегментного возврата. Когда управление возвращается программе ROOT, она восстанавливает содержимое регистра сегмента DS так, чтобы он указывал опять на ее собственный сегмент данных, и выводит на экран дополнительное сообще- ние, также используя для этого функцию 40H прерывания 21H, для указа- ния того, что оверлейная программа завершилась успешно. После этого ROOT заканчивает свою работу, используя функцию 4CH прерывания 21H, передавая COMMAND.COM управление и равный нулю код возврата для инди- кации успешного завершения. Рэй Дункан